畅购商城4.0

畅购商城4.0
1.走进电商
1.1电商行业分析
近年来,世界经济正向数字化转型,大力发展数字经济成为全球共识。党的十九大报告明确提出要建设“数字中国”“网络强国”,我国数字经济发展进入新阶段,市场规模位居全球第二,数字经济与实体经济深度融合,有力促进了供给侧结构性改革。电子商务是数字经济的重要组成部分,是数字经济最活跃、最集中的表现形式之一。
我国电子商务交易规模继续扩大,全国电子商务交易额达保持高速增长。国家统计局数据显示,2017年29.16万亿元,2018年31.64万亿元,2019年34.81万亿元,2020年交易额达38.21万亿元。
最近几年天猫双十一成交额

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

C2C
C2C即 Customer(Consumer) to Customer(Consumer),意思就是消费者个人间的电子商务行为。比如一个消费者有一台电脑,通过网络进行交易,把它出售给另外一个消费者,此种交易类型就称为C2C电子商务。
案例:淘宝、易趣、瓜子二手车

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

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

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

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

B2B2C
B2B2C是一种电子商务类型的网络购物商业模式,B是BUSINESS的简称,C是CUSTOMER的简称,第一个B指的是商品或服务的供应商,第二个B指的是从事电子商务的企业,C则是表示消费者。

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

注:我们《畅购电商系统开发》课程采用B2C模式

2.畅购前台-需求分析与系统设计
电商项目分为:前台和后台
前台:提供用户使用
后台:提供商家进行管理
本项目主要讲解时:前台
2.1需求分析

2.2系统设计
2.2.1技术架构

2.2.2架构图

3.架构搭建
3.1数据库环境

本项目的重点在前端和后端,提供的数据库,没有分库分表。

3.2后端环境
3.2.1父工程:changgou4_parent_ali
修改pom.xml文件,确定spring boot、spring cloud、spring cloud Alibaba 等版本

<?xml version="1.0" encoding="UTF-8"?>


4.0.0

<groupId>com.czxy.changgou</groupId>
<artifactId>changgou4-parent-ali</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
    <module>changgou4_common</module>
    <module>changgou4_common_auth</module>
    <module>changgou4_common_db</module>
    <module>changgou4_gateway</module>
    <module>changgou4_pojo</module>
    <module>changgou4_service_web</module>
</modules>

<!-- 1 确定spring boot的版本-->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.5.RELEASE</version>
</parent>

<!--2  确定版本-->
<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>1.8</java.version>
    <spring-cloud-release.version>Hoxton.SR3</spring-cloud-release.version>
    <nacos.version>1.1.0</nacos.version>
    <alibaba.cloud.version>2.2.1.RELEASE</alibaba.cloud.version>
    <mysql.version>5.1.32</mysql.version>
    <mybatis.plus.version>3.4.0</mybatis.plus.version>
    <druid.starter.version>1.1.9</druid.starter.version>
    <jwt.jjwt.version>0.9.0</jwt.jjwt.version>
    <jwt.joda.version>2.9.7</jwt.joda.version>
    <swagger.version>2.7.0</swagger.version>
    <beanutils.version>1.9.3</beanutils.version>
    <aliyun.sdk.core.version>3.3.1</aliyun.sdk.core.version>
    <aliyun.sdk.dysmsapi.version>1.0.0</aliyun.sdk.dysmsapi.version>
    <changgou4.common.version>1.0-SNAPSHOT</changgou4.common.version>
    <changgou4.common.auth.version>1.0-SNAPSHOT</changgou4.common.auth.version>
    <changgou4.common.db.version>1.0-SNAPSHOT</changgou4.common.db.version>
    <changgou4.pojo.version>1.0-SNAPSHOT</changgou4.pojo.version>

</properties>

<!-- 3 锁定版本-->
<dependencyManagement>
    <dependencies>
        <!-- sprig cloud-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud-release.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!--nacos -->
        <dependency>
            <groupId>com.alibaba.nacos</groupId>
            <artifactId>nacos-client</artifactId>
            <version>${nacos.version}</version>
        </dependency>

        <!--nacos cloud 发现 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <version>${alibaba.cloud.version}</version>
        </dependency>

        <!--nacos cloud 配置 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            <version>${alibaba.cloud.version}</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-sentinel -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            <version>${alibaba.cloud.version}</version>
        </dependency>

        <!-- mybatis plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis.plus.version}</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-annotation</artifactId>
            <version>${mybatis.plus.version}</version>
        </dependency>

        <!-- mysql驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>

        <!-- druid启动器 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid.starter.version}</version>
        </dependency>

        <!--swagger2-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>${swagger.version}</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>${swagger.version}</version>
        </dependency>

        <!--jwt-->
        <!--JavaBean工具类,用于JavaBean数据封装-->
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>${beanutils.version}</version>
        </dependency>

        <!--jwt工具-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>${jwt.jjwt.version}</version>
        </dependency>

        <!--joda 时间工具类 -->
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>${jwt.joda.version}</version>
        </dependency>

        <!--短信-->
        <dependency>
            <groupId>com.aliyuncs</groupId>
            <artifactId>aliyun-java-sdk-core</artifactId>
            <version>${aliyun.sdk.core.version}</version>
        </dependency>
        <dependency>
            <groupId>com.aliyuncs.dysmsapi</groupId>
            <artifactId>aliyun-java-sdk-dysmsapi</artifactId>
            <version>${aliyun.sdk.dysmsapi.version}</version>
        </dependency>

        <!--自定义项目-->
        <dependency>
            <groupId>com.czxy.changgou</groupId>
            <artifactId>changgou4_common</artifactId>
            <version>${changgou4.common.version}</version>
        </dependency>
        <dependency>
            <groupId>com.czxy.changgou</groupId>
            <artifactId>changgou4_common_auth</artifactId>
            <version>${changgou4.common.auth.version}</version>
        </dependency>
        <dependency>
            <groupId>com.czxy.changgou</groupId>
            <artifactId>changgou4_common_db</artifactId>
            <version>${changgou4.common.db.version}</version>
        </dependency>
        <dependency>
            <groupId>com.czxy.changgou</groupId>
            <artifactId>changgou4_pojo</artifactId>
            <version>${changgou4.pojo.version}</version>
        </dependency>


    </dependencies>

</dependencyManagement>

3.2.2公共项目(基础):changgou4-common

通用工具项目
基于spring cloud开发基本依赖
web开发常见的工具类

步骤一:修改pom.xml文件,添加依赖

<?xml version="1.0" encoding="UTF-8"?>



changgou4-parent-ali
com.czxy.changgou
1.0-SNAPSHOT

4.0.0

<artifactId>changgou4-common</artifactId>

<dependencies>
    <!--短信-->
    <dependency>
        <groupId>com.aliyuncs</groupId>
        <artifactId>aliyun-java-sdk-core</artifactId>
    </dependency>
    <dependency>
        <groupId>com.aliyuncs.dysmsapi</groupId>
        <artifactId>aliyun-java-sdk-dysmsapi</artifactId>
    </dependency>
    <!--lombok-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>

步骤二:拷贝工具类

3.2.3公共项目(认证):changgou4-common-auth
认证通用工具项目

步骤一:修改pom.xml文件

<?xml version="1.0" encoding="UTF-8"?>



changgou4-parent-ali
com.czxy.changgou
1.0-SNAPSHOT

4.0.0

<artifactId>changgou4-common-auth</artifactId>

<dependencies>
    <!--通用基础-->
    <dependency>
        <groupId>com.czxy.changgou</groupId>
        <artifactId>changgou4_common</artifactId>
    </dependency>
    <!--JavaBean工具类,用于JavaBean数据封装-->
    <dependency>
        <groupId>commons-beanutils</groupId>
        <artifactId>commons-beanutils</artifactId>
    </dependency>

    <!--jwt工具-->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
    </dependency>

    <!--joda 时间工具类 -->
    <dependency>
        <groupId>joda-time</groupId>
        <artifactId>joda-time</artifactId>
    </dependency>
</dependencies>

步骤二:拷贝工具类

3.2.4公共项目(数据库):changgou4-common-db
数据库通用工具项目

步骤一:修改pom.xml文件

<?xml version="1.0" encoding="UTF-8"?>



changgou4-parent-ali
com.czxy.changgou
1.0-SNAPSHOT

4.0.0

<artifactId>changgou4-common-db</artifactId>

<dependencies>
    <!--通用基础-->
    <dependency>
        <groupId>com.czxy.changgou</groupId>
        <artifactId>changgou4-common</artifactId>
    </dependency>
    <!-- mybatis plus-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
    </dependency>
    <!-- mysql驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <!-- druid启动器 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
    </dependency>
</dependencies>

步骤二:配置类
要求所有的服务项目包名必须是“com.czxy.changgou4”,否则配置无法扫描,就需要每个项目单独拷贝。

3.2.5POJO项目:changgou4-pojo
统一管理所有的JavaBean

修改pom.xml文件



org.projectlombok
lombok



org.springframework.boot
spring-boot-starter-json



com.baomidou
mybatis-plus-annotation

3.2.6网关:changgou4-gateway
修改pom.xml文档

<?xml version="1.0" encoding="UTF-8"?>



changgou4-parent-ali
com.czxy.changgou
1.0-SNAPSHOT

4.0.0

<artifactId>changgou4-gateway</artifactId>

<dependencies>
    <!--自定义项目-->
    <dependency>
        <groupId>com.czxy.changgou</groupId>
        <artifactId>changgou4-common-auth</artifactId>
    </dependency>
    <dependency>
        <groupId>com.czxy.changgou</groupId>
        <artifactId>changgou4-pojo</artifactId>
    </dependency>
    <!-- 网关 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>

    <!-- nacos 服务发现 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
</dependencies>

创建application.yml文档
#端口号
server:
port: 10010
spring:
application:
name: changgou4-gateway
servlet:
multipart:
max-file-size: 2MB #上传文件的大小
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 #nacos服务地址
gateway:
discovery:
locator:
enabled: true #开启服务注册和发现的功能,自动创建router以服务名开头的请求路径转发到对应的服务
lowerCaseServiceId: true #将请求路径上的服务名配置为小写

拷贝跨域配置类 GlobalCorsConfig

package com.czxy.changgou4.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

/**

  • @author 桐叔

  • @email [email protected]
    */
    @Configuration
    public class GlobalCorsConfig {

    @Bean
    public WebFilter corsFilter2() {
    return (ServerWebExchange ctx, WebFilterChain chain) -> {
    ServerHttpRequest request = ctx.getRequest();
    if (CorsUtils.isCorsRequest(request)) {
    HttpHeaders requestHeaders = request.getHeaders();
    ServerHttpResponse response = ctx.getResponse();
    HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod();
    HttpHeaders headers = response.getHeaders();
    headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());
    headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS,
    requestHeaders.getAccessControlRequestHeaders());
    if (requestMethod != null) {
    headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());
    }
    headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, “true”);
    headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, “*”);
    if (request.getMethod() == HttpMethod.OPTIONS) {
    response.setStatusCode(HttpStatus.OK);
    return Mono.empty();
    }
    }
    return chain.filter(ctx);
    };
    }

}

创建启动类

package com.czxy.changgou4;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**

  • @author 桐叔
  • @email [email protected]
    */
    @SpringBootApplication
    @EnableDiscoveryClient
    public class CGGatewayApplication {
    public static void main(String[] args) {
    SpringApplication.run(CGGatewayApplication.class, args );
    }
    }

3.3前端环境
3.3.1构建项目:changgou4-fore
步骤一:使用脚手架构建项目
npx create-nuxt-app changgou4-fore

步骤二:npm打包方式、axios第三方模块、SSR渲染模式

3.3.2整合axios
步骤一:创建~/plugins/apiclient.js文件
步骤二:编写nuxt整合模板,用于统一管理ajax请求路径
const request = {
test : ()=> {
return axios.get(‘/test’)
}
}

var axios = null
export default ({ $axios, redirect }, inject) => {

//赋值
axios = $axios

//4) 将自定义函数交于nuxt
// 使用方式1:在vue中,this. r e q u e s t . x x x ( ) / / 使用方式 2 :在 n u x t 的 a s y n c D a t a 中, c o n t e n t . a p p . request.xxx() // 使用方式2:在nuxt的asyncData中,content.app. request.xxx()//使用方式2:在nuxtasyncData中,content.app.request.xxx()
inject(‘request’, request)
}

步骤三:配置apiclient.js插件,修改nuxt.conf.js配置文件完成操作

plugins: [
{ src: ‘~plugins/apiclient.js’}
],

步骤四:修改nuxt.conf.js配置文件,配置axios通用设置

axios: {
baseURL: ‘http://localhost:10010’
},

步骤五:测试 apiclient.js是否配置成功,访问test时,出现404
async mounted() {
let { data } = await this.$request.test()
console.info(data)
},

3.3.3拷贝静态资源
将所有静态资源拷贝到static目录中

通过浏览器访问静态页面
http://localhost:3000/index.html

3.3.4修改Nuxt项目默认项
1)修改默认布局,删除已有样式

2)删除pages目录下的所有内容

3.3.5配置公共js和css
修改默认布局,添加公共js和css

4.用户模块(8081)
4.1搭建环境
4.1.1后端web服务:changgou4-service-web
修改pom.xml文档

<?xml version="1.0" encoding="UTF-8"?>



changgou4-parent-ali
com.czxy.changgou
1.0-SNAPSHOT

4.0.0

<artifactId>changgou4_service_web</artifactId>

<dependencies>
    <!--自定义项目-->
    <dependency>
        <groupId>com.czxy.changgou</groupId>
        <artifactId>changgou4_common_db</artifactId>
    </dependency>
    <dependency>
        <groupId>com.czxy.changgou</groupId>
        <artifactId>changgou4_pojo</artifactId>
    </dependency>
    <!--web起步依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- nacos 客户端 -->
    <dependency>
        <groupId>com.alibaba.nacos</groupId>
        <artifactId>nacos-client</artifactId>
    </dependency>

    <!-- nacos 服务发现 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!--redis-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
    </dependency>
    <!--swagger2-->
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger2</artifactId>
    </dependency>
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger-ui</artifactId>
    </dependency>
</dependencies>

创建application.yml文档

#端口号
server:
port: 8081

spring:
application:
name: web-service #服务名
datasource:
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/changgou_db?useUnicode=true&characterEncoding=utf8
username: root
password: 1234
druid: #druid 连接池配置
initial-size: 1 #初始化连接池大小
min-idle: 1 #最小连接数
max-active: 20 #最大连接数
test-on-borrow: true #获取连接时候验证,会影响性能
redis:
database: 0
host: 127.0.0.1
port: 6379
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 #nacos服务地址
sentinel:
transport:
dashboard: 127.0.0.1:8080

创建启动类

package com.czxy.changgou4;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**

  • @author 桐叔
  • @email [email protected]
    */
    @SpringBootApplication
    @EnableDiscoveryClient
    public class Web-serviceApplication {
    public static void main(String[] args) {
    SpringApplication.run( Web-serviceApplication.class , args );
    }
    }

4.1.2后端创建JavaBean:User
在changgou4-pojo项目中添加User对象

package com.czxy.changgou4.pojo;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.beans.Transient;
import java.util.Date;

/** 与数据库对应JavaBean

  • Created by liangtong.
    /
    @TableName(“tb_user”)
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class User {
    /

    CREATE TABLE tb_user (
    id int(10) unsigned NOT NULL AUTO_INCREMENT,
    created_at timestamp NULL DEFAULT NULL,
    updated_at timestamp NULL DEFAULT NULL,
    email varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT ‘Email’,
    mobile varchar(20) COLLATE utf8_unicode_ci NOT NULL COMMENT ‘手机号码’,
    username varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT ‘昵称’,
    password char(60) COLLATE utf8_unicode_ci NOT NULL COMMENT ‘密码’,
    face varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT ‘头像’,
    expriece int(10) unsigned DEFAULT ‘0’ COMMENT ‘经验值’,
    PRIMARY KEY (id),
    UNIQUE KEY users_mobile_unique (mobile),
    UNIQUE KEY users_name_unique (name),
    UNIQUE KEY users_email_unique (email)
    ) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
    */
    @TableId(value=“id”,type = IdType.AUTO)
    private Long id;

    @TableField(value=“username”)
    private String username;

    @TableField(value=“password”)
    private String password;

    @TableField(value=“face”)
    private String face;

    @TableField(value=“expriece”)
    private Integer expriece;

    @TableField(value=“email”)
    private String email;

    @TableField(value=“mobile”)
    private String mobile;

    @TableField(value=“created_at”)
    private Date createdAt;

    @TableField(value=“updated_at”)
    private Date updatedAt;

    @TableField(exist = false)
    private String code;
    @TableField(exist = false)
    private String password_confirm;

}

4.1.3前端页面:创建公共组件
1)删除components目录下所有内容,并创建3个新组件

2)创建 TopNav.vue组件,用于配置“顶部导航”

  
    
      


      


        

  •           
  • 您好,欢迎来到畅购![登录] [免费注册]

  •           
  • |

  •           
  • 我的订单

  •           
  • |

  •           
  • 客户服务


      
    
  
  

3)创建 HeaderLogo.vue组件,用于配置“页面头部,仅有LOGO”

  
       
  

4)创建 Footer.vue组件,用于配置“底部版权”

     

4.2用户注册:用户名占用

4.2.1接口
http://localhost:10010/web-service/user/checkusername
{
“username”:“jack1”
}

4.2.2后端
创建三层需要的接口或类

步骤一:创建UserMapper,编写findByUsername()完成“通过用户名查询用户”
package com.czxy.changgou4.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.czxy.changgou4.pojo.User;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

/**

  • Created by liangtong.
    /
    @org.apache.ibatis.annotations.Mapper
    public interface UserMapper extends BaseMapper {
    /
    *
    • 通过用户名查询
    • @param username
    • @return
      */
      @Select(“select * from tb_user where username = #{username}”)
      User findByUsername(@Param(“username”) String username);
      }

步骤二:创建UserService接口,查询功能

package com.czxy.changgou4.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.czxy.changgou4.pojo.User;

/**

  • @author 桐叔
  • @email [email protected]
    /
    public interface UserService extends IService {
    /
    *
    • 通过用户名查询
    • @param username
    • @return
      */
      public User findByUsername(String username);
      }

步骤三:创建UserServiceImpl实现类,查询功能
package com.czxy.changgou4.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.czxy.changgou4.mapper.UserMapper;
import com.czxy.changgou4.pojo.User;
import com.czxy.changgou4.service.UserService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**

  • @author 桐叔
  • @email [email protected]
    */
    @Service
    @Transactional
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    @Override
    public User findByUsername(String username) {
    return baseMapper.findByUsername(username);
    }
    }

步骤四:创建UserController,完成用户名检查
package com.czxy.changgou4.controller;

import com.czxy.changgou4.pojo.User;
import com.czxy.changgou4.service.UserService;
import com.czxy.changgou4.vo.BaseResult;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

/**

  • Created by liangtong.
    */
    @RestController
    @RequestMapping(“/user”)
    public class UserController {

    @Resource
    private UserService userService;

    @PostMapping(“/checkusername”)
    public BaseResult checkUsername(@RequestBody User user){
    //查询用户
    User findUser = userService.findByUsername( user.getUsername() );
    //判断
    if(findUser != null){
    return BaseResult.error(“用户名已经存在”);
    } else {
    return BaseResult.ok(“用户名可用”);
    }
    }

}

4.2.3前端
步骤一:创建Register.vue

步骤二:添加公共组件

步骤三:编写注册表单,并导入独有样式

     


  
  


步骤四:修改api.js,编写检查用户名的ajax函数
const request = {
test : ()=> {
return axios.get(‘/test’)
},
//检查用户名
checkUsername : ( username )=> {
return axios.post(‘/web-service/user/checkusername’, { username })
}
}

步骤五:修改Register.vue页面,完成检查功能
发送ajax进行用户名是否可用检查
如果可用,显示对应信息,并使用success样式显示
如果不可用,显示对应信息,并使用error样式提示

     


  
  


4.3用户注册:手机号检查
4.3.1接口
http://localhost:10010/web-service/user/checkmobile
{
“mobile”:“13344445555”
}

4.3.2后端

步骤一:修改UserService,添加 findByMobile() 方法,进行电话号码的查询
/**

  • 通过手机号查询
  • @param mobile
  • @return
    */
    User findByMobile(String mobile);

步骤二:编写UserServiceImpl,实现findByMobile() 方法
@Override
public User findByMobile(String mobile) {
// 拼凑条件
QueryWrapper queryWrapper = new QueryWrapper();
queryWrapper.eq(“mobile”, mobile);
// 查询一个
List list = baseMapper.selectList(queryWrapper);
if(list.size() == 1) {
return list.get(0);
}
return null;
}

步骤三:修改UserController,添加checkMobile() 方法
/**

  • 通过手机号查询
  • @param user
  • @return
    */
    @PostMapping(“/checkmobile”)
    public BaseResult checkMobile(@RequestBody User user){
    //查询用户
    User findUser = userService.findByMobile( user.getMobile() );
    //判断
    if(findUser != null){
    return BaseResult.error(“电话号码已经注册”);
    } else {
    return BaseResult.ok(“电话号码可用”);
    }
    }

4.3.3前端
步骤一:修改api.js,添加 checkMobile() 函数

const request = {
test : ()=> {
return axios.get(‘/test’)
},
//检查用户名
checkUsername : ( username )=> {
return axios.post(‘/web-service/user/checkusername’, { username })
},
//检查电话号码
checkMobile : ( mobile )=> {
return axios.post(‘/web-service/user/checkmobile’, { mobile })
}
}

步骤二:修改Register.vue,添加 checkMobileFn() 进行手机号检查

methods: {
async checkUsernameFn() {
//检查用户名
let {data} = await this.KaTeX parse error: Expected 'EOF', got '}' at position 88: …ata = data }̲, async che…request.checkMobile( this.user.mobile )
this.userMsg.mobileData = data
}
},

步骤三:编写需要的2个变量
data() {
return {
user : { //表单封装数据
        username : “”,
        mobile : “”
},
userMsg : { //错误提示数据
usernameData : “”,
mobileData : “”
}
}
},

步骤四:处理页面

  • 完整版

         


      
      


    4.4用户注册:前置技术–Redis

    4.5用户注册:前置技术–阿里大鱼

    4.6用户注册:短信验证码
    4.6.1分析

    4.6.2接口
    http://localhost:10010/web-service/sms
    {
    “mobile”:“13344445555”,
    “username”: “jack”
    }

    4.6.3后端
    创建 SmsController类,调用阿里大鱼工具类,发送短信。

    package com.czxy.changgou4.controller;

    import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
    import com.czxy.changgou4.pojo.User;
    import com.czxy.changgou4.utils.SmsUtil;
    import com.czxy.changgou4.vo.BaseResult;
    import org.apache.commons.lang.RandomStringUtils;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;

    import javax.annotation.Resource;
    import java.util.concurrent.TimeUnit;

    /**

    • Created by liangtong.
      */
      @RestController
      @RequestMapping(“/sms”)
      public class SmsController {

      @Resource
      private StringRedisTemplate redisTemplate;

      @PostMapping
      public BaseResult sendSms(@RequestBody User user){
      long start = System.currentTimeMillis();
      try {
      //发送短信
      //1 生产验证码
      String code = RandomStringUtils.randomNumeric(4);
      System.out.println(“验证码:” + code);

           //2 并存放到reids中 , key: "sms_register" + 手机号 , value:验证码 , 1小时
           redisTemplate.opsForValue().set( "sms_register" + user.getMobile() , code , 1 , TimeUnit.HOURS);
      
           /**/
           //3 发送短信
           SendSmsResponse smsResponse = SmsUtil.sendSms(user.getMobile(), user.getUsername() , code, "", "");
      
           //https://help.aliyun.com/document_detail/55284.html?spm=5176.doc55322.6.557.KvvIJx
           if("OK".equalsIgnoreCase(smsResponse.getCode())){
               return BaseResult.ok("发送成功");
           } else {
               return BaseResult.error(smsResponse.getMessage());
           }
      
           /*
           //模拟数据
           System.out.println("验证码:" + code);
           return BaseResult.ok("发送成功");
           */
       } catch (Exception e) {
           long end = System.currentTimeMillis();
           System.out.println( end - start);
           return BaseResult.error("发送失败" );
       }
      

      }
      }

    4.6.4前端
    步骤一:修改apiclient.js,发送短信ajax操作

    //发短信
    sendSms : ( user )=> {
    return axios.post(‘/web-service/sms’, user )
    }

    步骤二:修改Register.vue页面,给“发送验证码”绑定点击事件 sendSmsFn
    <button @click.prevent=“sendSmsFn” >
    发送验证码5秒

    步骤三:修改Register.vue页面,编写sendSmsFn函数,建议采用 ajax…then()…catch 可以处理异常

    sendSmsFn () {
    this.$request.sendSms( this.user )
    .then(( response )=>{
    //发送短信的提示信息
    this.userMsg.smsData = response.data
    })
    .catch(( error )=>{
    //错误提示信息
    alert( error.message )
    })
    }

    步骤四:修改Register.vue页面,提供变量smsData

    userMsg : { //错误提示数据
    usernameData : “”,
    mobileData : “”,
    smsData : “”
    }

    步骤五:修改Register.vue页面,显示 smsData提示信息

    { {userMsg.smsData.message}}

    4.6.5倒计时
    步骤一:提供3个变量,用于控制倒计时

      btnDisabled : false,  //倒计时控制变量
    

    seconds : 5, //默认倒计时秒数
    timer : null,      //接收定时器,清除定时器

    步骤二:在标签上面控制倒计时的显示
    <button :disabled=“btnDisabled” @click.prevent=“sendSmsFn” >
    发送验证码{ {seconds}}秒

    步骤三:发送短信后,开启倒计时控制
    sendSmsFn () {
    this.$request.sendSms( this.user )
    .then(( response )=>{
    //发送短信的提示信息
    this.userMsg.smsData = response.data
    //按钮不可用
    this.btnDisabled = true;
    //倒计时
    this.timer = setInterval( ()=>{
    if(this.seconds <= 1){
    //结束
    // 重置秒数
    this.seconds = 5;
    // 按钮可用
    this.btnDisabled = false;
    // 停止定时器
    clearInterval(this.timer);
    } else {
    this.seconds --;
    }
    } , 1000);
    })
    .catch(( error )=>{
    //错误提示信息
    alert( error.message )
    })
    }

    4.7用户注册
    4.7.1接口
    http://localhost:10010/web-service/user/register
    {
    “mobile”:“13612345677”,
    “password”:“1234”,
    “username”:“jack3”,
    “code”:“3919”
    }

    4.7.2后端
    保存前需要再次进行服务端校验
    用户名是否注册
    手机号是否注册
    验证码是否失效
    验证码是否错误
    密码需要使用 BCrypt进行加密

    步骤一:修改UserService接口,添加register方法
    /**

    • 用户注册
    • @param user
    • @return
      */
      public boolean register(User user) ;

    步骤二:完善UserServiceImpl实现类

    @Override
    public boolean register(User user) {
    //密码加密
    String newPassword = BCrypt.hashpw(user.getPassword());
    user.setPassword(newPassword);

    //处理数据
    user.setCreatedAt(new Date());
    user.setUpdatedAt(user.getCreatedAt());
    
    int insert = baseMapper.insert(user);
    
    return insert == 1;
    

    }

    步骤三:修改UserController,添加register方法

    /**

    • 用户注册

    • @param user

    • @return
      */
      @PostMapping(“/register”)
      public BaseResult register(@RequestBody User user){

      //服务端校验
      User findUser = userService.findByUsername(user.getUsername());
      if(findUser != null) {
      return BaseResult.error(“用户名已经存在”);
      }

      findUser = userService.findByMobile(user.getMobile());
      if(findUser != null) {
      return BaseResult.error(“电话号码已经存在”);
      }

      //验证码
      String code = stringRedisTemplate.opsForValue().get(“sms_register” + user.getMobile());
      //删除redis中的验证码
      stringRedisTemplate.delete(“sms_register” + user.getMobile());
      if(code == null) {
      return BaseResult.error(“验证码失效”);
      }
      if(!code.equals(user.getCode())) {
      return BaseResult.error(“验证码不正确”);
      }

      //注册
      boolean register = userService.register(user);

      if(register) {
      return BaseResult.ok(“注册成功”);
      }
      return BaseResult.error(“注册失败”);
      }

    4.7.3日期处理(可选)
    编写 DateMetaObjectHandler 用于处理“创建时间”和“修改日期”
    package com.czxy.changgou4.handler;

    import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
    import org.apache.ibatis.reflection.MetaObject;
    import org.springframework.stereotype.Component;

    import java.util.Date;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @Component
      public class DateMetaObjectHandler implements MetaObjectHandler {
      @Override
      public void insertFill(MetaObject metaObject) {
      this.setFieldValByName(“createdAt”, new Date(), metaObject);
      this.setFieldValByName(“updatedAt”, new Date(), metaObject);
      }

      @Override
      public void updateFill(MetaObject metaObject) {
      this.setFieldValByName(“updatedAt”, new Date(), metaObject);
      }
      }

    完善User JavaBean,设置填充方式
    @TableField(value=“created_at”,fill = FieldFill.INSERT)
    private Date createdAt;

    @TableField(value=“updated_at”,fill = FieldFill.INSERT_UPDATE)
    private Date updatedAt;

    4.7.4前端
    步骤一:修改 api.js ,添加注册函数

    //注册
    register : ( user )=> {
    return axios.post(‘/web-service/user/register’, user )
    }

    步骤二:处理表单,验证码input绑定数据,提交按钮绑定事件


  •               
                  
                  <button :disabled=“btnDisabled” @click.prevent=“sendSmsFn” >
    发送验证码{ {seconds}}秒

    { {userMsg.smsData.message}}


                

  •             

  •               
                   我已阅读并同意《用户注册协议》
                

  •             

  •               
                  <input type=“submit” value=“” @click.prevent=“registerFn” class=“login_btn” />
                
  • 步骤三:完善data区域的user数据

      user : {  //表单封装数据
    

    username : “”, //用户名
            mobile : “13699282444”, //手机号
            password : “”, //密码
            code : “” //验证码
    },

    步骤四:编写registerFn函数
    async registerFn() {
    let { data } = await this.KaTeX parse error: Expected '}', got 'EOF' at end of input: …功 this.router.push(‘/login’)
    } else {
    //失败–与发送验证码使用一个位置显示错误信息
    this.userMsg.smsData = data
    }
    }

    4.8用户登录
    4.8.1构建页面:Login.vue
    步骤一:创建Login.vue

    步骤二:绘制通用模块

    <div style="clear:both;"></div>
    <Footer></Footer>
    

    步骤三:绘制登录表单

    4.8.2分析

    4.8.3验证码:接口
    http://localhost:10010/web-service/verifycode?username=jack

    4.8.4验证码:生成与显示
    步骤一:后端生产验证码,并将用户保存Redis
    存放redis中验证码key格式:“login” + 用户名

    package com.czxy.changgou4.controller;

    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;

    import javax.annotation.Resource;
    import javax.imageio.ImageIO;
    import javax.servlet.http.HttpServletResponse;
    import java.awt.*;
    import java.awt.image.BufferedImage;
    import java.io.IOException;
    import java.util.Random;
    import java.util.concurrent.TimeUnit;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @Controller
      @RequestMapping(“/verifycode”)
      public class VerifyCodeController {

      @Resource
      private StringRedisTemplate stringRedisTemplate;

      @GetMapping
      public void verifyCode(String username , HttpServletResponse response ) throws IOException {

       //字体只显示大写,去掉了1,0,i,o几个容易混淆的字符
       String VERIFY_CODES = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
      
       int IMG_WIDTH = 72;
       int IMG_HEIGTH = 27;
       Random random = new Random();
       //创建图片
       BufferedImage image = new BufferedImage(IMG_WIDTH, IMG_HEIGTH, BufferedImage.TYPE_INT_RGB);
       //画板
       Graphics g = image.getGraphics();
       //填充背景
       g.setColor(Color.WHITE);
       g.fillRect(1,1,IMG_WIDTH-2,IMG_HEIGTH-2);
      
       g.setFont(new Font("楷体", Font.BOLD,25));
      
       StringBuilder sb = new StringBuilder();
       //写字
       for(int i = 1 ; i <= 4 ; i ++){
           //随机颜色
           g.setColor(new Color(random.nextInt(255),random.nextInt(255),random.nextInt(255)));
           int len = random.nextInt(VERIFY_CODES.length());
           String str = VERIFY_CODES.substring(len,len+1);
           sb.append(str);
           g.drawString(str, IMG_WIDTH / 6 * i , 22 );
       }
      
       //将验证码存放到redis
       stringRedisTemplate.opsForValue().set( "login" + username , sb.toString() , 1 , TimeUnit.HOURS);
      
      
       // 生成随机干扰线
       for (int i = 0; i < 30; i++) {
           //随机颜色
           g.setColor(new Color(random.nextInt(255),random.nextInt(255),random.nextInt(255)));
           int x = random.nextInt(IMG_WIDTH - 1);
           int y = random.nextInt(IMG_HEIGTH - 1);
           int x1 = random.nextInt(12) + 1;
           int y1 = random.nextInt(6) + 1;
           g.drawLine(x, y, x - x1, y - y1);
       }
      
       //响应到浏览器
       ImageIO.write(image,"jpeg", response.getOutputStream());
      

      }
      }

    步骤二:点击“换一张”显示验证码
    默认不显示验证码
    点击“换一张”获得验证码

    4.8.5通过用户名查询:接口
    http://localhost:10010/web-service/user/findByUsername
    {
    “username”:“jack”
    }

    4.8.6通过用户名查询:实现
    修改UserController,添加 findByUsername函数

    /**

    • 通过用户名查询
    • @param user
    • @return 返回用户对象
      */
      @PostMapping(“/findByUsername”)
      public User findByUsername(@RequestBody User user){
      //查询用户
      User findUser = userService.findByUsername( user.getUsername() );
      return findUser;
      }

    4.8.7认证服务:构建项目(changgou4-service-auth)
    步骤一:构建项目

    步骤二:创建pom.xml文件

    com.czxy.changgou changgou4_common_auth
    <!--web起步依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- nacos 客户端 -->
    <dependency>
        <groupId>com.alibaba.nacos</groupId>
        <artifactId>nacos-client</artifactId>
    </dependency>
    
    <!-- nacos 服务发现 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!--redis-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
    </dependency>
    <!--swagger2-->
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger2</artifactId>
    </dependency>
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger-ui</artifactId>
    </dependency>
    

    步骤三:创建yml文件
    server:
    port: 8085
    spring:
    application:
    name: auth-service
    cloud:
    nacos:
    discovery:
    server-addr: 127.0.0.1:8848 #nacos服务地址

    sc:
    jwt:
    secret: sc@Login(Auth}*^31)&czxy% # 登录校验的密钥
    pubKeyPath: D:/rsa/rsa.pub # 公钥地址
    priKeyPath: D:/rsa/rsa.pri # 私钥地址
    expire: 360 # 过期时间,单位分钟

    步骤四:配置启动类

    package com.czxy.changgou4;

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.openfeign.EnableFeignClients;

    /**

    • @author 桐叔
    • @email [email protected]
      */
      @SpringBootApplication
      @EnableDiscoveryClient
      @EnableFeignClients
      public class CGAuthServiceApplication {
      public static void main(String[] args) {
      SpringApplication.run(CGAuthServiceApplication.class, args);
      }
      }

    步骤五:配置类

    4.8.8认证服务:用户登录后端
    步骤一:创建AuthUser 封装对象(与User比较,缺数据库相关注解)
    package com.czxy.changgou4.domain;

    import lombok.Data;

    import java.util.Date;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @Data
      public class AuthUser {
      private Long id;

      private String username;

      private String password;

      private String face;

      private Integer expriece;

      private String email;

      private String mobile;

      private Date createdAt;

      private Date updatedAt;

      private String code;

      private String password_confirm;
      }

    步骤二:创建UserFeign,完成远程用户查询功能
    package com.czxy.changgou4.feign;

    import com.czxy.changgou4.domain.AuthUser;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @FeignClient(value = “web-service”,path=“/user”)
      public interface UserFeign {

      @PostMapping(“/findByUsername”)
      public AuthUser findByUsername(@RequestBody AuthUser user);

    }

    步骤三:创建AuthService接口,编写登录方法 login()
    package com.czxy.changgou4.service;

    import com.czxy.changgou4.domain.AuthUser;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      public interface AuthService {

      /**

      • 用户登录
      • @param user
      • @return
        */
        public AuthUser login(AuthUser user ) ;
        }

    步骤四:创建AuthService实现类,并通过BCrypt校验密码

    package com.czxy.changgou4.service.impl;

    import com.czxy.changgou4.domain.AuthUser;
    import com.czxy.changgou4.feign.UserFeign;
    import com.czxy.changgou4.service.AuthService;
    import com.czxy.changgou4.utils.BCrypt;
    import org.springframework.stereotype.Service;

    import javax.annotation.Resource;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @Service
      public class AuthServiceImpl implements AuthService {
      @Resource
      private UserFeign userFeign;

      /**

      • 用户登录
      • @param user
      • @return
        */
        public AuthUser login(AuthUser user ) {
        //远程查询用户
        AuthUser findUser = userFeign.findByUsername(user);
        if(findUser == null) {
        return null;
        }
        //校验密码是否正确
        boolean checkpw = BCrypt.checkpw( user.getPassword(), findUser.getPassword());
        if(checkpw){
        return findUser;
        }
        return null;
        }

    }

    步骤五:创建AuthController,添加login方法
    redis中登录验证码和用户输入的验证码进行匹配

    package com.czxy.changgou4.controller;

    /**

    import com.czxy.changgou4.domain.AuthUser;
    import com.czxy.changgou4.service.AuthService;
    import com.czxy.changgou4.vo.BaseResult;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;

    import javax.annotation.Resource;

    /**

    • Created by liangtong.
      */
      @RestController
      @RequestMapping(“/auth”)
      public class AuthController {

      @Resource
      private AuthService authService;

      @Resource
      private StringRedisTemplate stringRedisTemplate;

      @PostMapping(“/login”)
      public BaseResult login(@RequestBody AuthUser user){
      //校验验证码–使用后删除
      String redisCode = stringRedisTemplate.opsForValue().get( “login” + user.getUsername() );
      stringRedisTemplate.delete( “login” + user.getUsername() );
      if(redisCode == null) {
      return BaseResult.error(“验证码无效”);
      }
      if(! redisCode.equalsIgnoreCase(user.getCode())) {
      return BaseResult.error(“验证码错误”);
      }
      //登录
      AuthUser loginUser = authService.login(user);
      if(loginUser != null ) {
      return BaseResult.ok(“登录成功”).append(“loginUser”,loginUser);
      } else {
      return BaseResult.error(“用户名或密码不匹配”);
      }
      }
      }

    4.8.9认证服务:用户登录前端
    步骤一:修改apiclient.js,添加login函数
    //登录
    login : ( user )=> {
    return axios.post(‘/auth-service/auth/login’, user )
    }

    步骤二:修改Login.vue,给验证码绑定变量

  • 步骤三:修改Login.vue,给提交按钮绑定事件

  • 步骤四:编写loginFn完成登录功能
    登录成功,跳转到首页
    登录失败,给出提示
    async loginFn() {
    let { data } = await this.KaTeX parse error: Expected '}', got 'EOF' at end of input: …页 this.router.push(‘/’)
    } else {
    this.errorMsg = data.message
    }
    }

    步骤五:创建首页 ~/pages/index.vue,

    步骤六:重命名静态页面 ~/static/index.html 为 ~/static/home.html

    4.8.10修改 TopNav.vue 组件

    完善导航条,根据vuex中的数据,显示不同内容

    步骤一:创建 ~/store/index.js ,并编写vuex内容

    export const state = () => ({
    user: null
    })

    //通用设置
    export const mutations = {
    setData( state , obj) {
    state[obj.key] = obj.value
    }
    }

    步骤二:页面登录成功,将用户信息保存到vuex中

    // 将用户信息保存到vuex中
    this.$store.commit(‘setData’, {key:‘user’,value: data.data })

    步骤三:修改顶部导航TopNav.vue
    从vuex中的数据

      
        
          


          


            

    •           
    • 您好,{ {user.username}} 欢迎来到畅购! <a href=“” @click.prevent=“logout”>退出

    • |

    •           
    • [登录] [免费注册]

    •           
    • |

    •           
    • 我的订单

    •           
    • |

    •           
    • 客户服务


          
        
      
      

    4.8.11vuex刷新数据丢失
    刷新操作:
    点击刷新按钮
    点击回退按钮
    地址栏直接输入地址
    现象:
    vuex在刷新操作后,数据丢失了
    解决方案
    方案1:不是公共组件:页面在pages目录下,可以nuxt.js提供 fetch进行操作。
    方案2:是公共组件:组件在components目录下,借助第三方进行存储(cookie、localStorage、sessionStorage)
    具体操作:
    如果vuex中没有数据,使用sessionStorage的数据填充vuex。
    修改TopNav.vue页面

      
        
          


          


            

    • 您好,{ {user.username}}欢迎来到畅购!

    •           

    • [登录]


    • [免费注册]


    • [退出]

    •           
    • |

    •           
    • 我的订单

    •           
    • |

    •           
    • 客户服务

    •         

          

        
      
      

    4.9整合JWT
    生成token:在用户登录成功,根据用户的登录信息,生成登录标识token,并返回给浏览器。
    使用token:完善ajax请求,在请求之前添加请求头,设置token
    校验token:在网关中编写过滤器,进行请求进行拦截,并校验token。
    白名单:在白名单中的请求,是不需要token可以直接访问的。
    4.9.1生成Token
    用户登录成功,生成token,并将token响应给浏览器。(认证服务 AuthService)
    步骤一:查看 application.yml文件,确定 jwt配置信息

    步骤二:创建JwtProperties文件,用于加载sc.jwt配置信息

    package com.czxy.changgou4.config;

    import com.czxy.changgou4.utils.RsaUtils;
    import lombok.Data;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.stereotype.Component;

    import javax.annotation.PostConstruct;
    import java.io.File;
    import java.security.PrivateKey;
    import java.security.PublicKey;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @Data
      @ConfigurationProperties(prefix = “sc.jwt”)
      @Component
      public class JwtProperties {

      private String secret; // 密钥

      private String pubKeyPath;// 公钥

      private String priKeyPath;// 私钥

      private int expire;// token过期时间

      private PublicKey publicKey; // 公钥

      private PrivateKey privateKey; // 私钥

      private static final Logger logger = LoggerFactory.getLogger(JwtProperties.class);

      @PostConstruct
      public void init(){
      try {
      File pubFile = new File(this.pubKeyPath);
      File priFile = new File(this.priKeyPath);
      if( !pubFile.exists() || !priFile.exists()){
      RsaUtils.generateKey( this.pubKeyPath ,this.priKeyPath , this.secret);
      }
      this.publicKey = RsaUtils.getPublicKey( this.pubKeyPath );
      this.privateKey = RsaUtils.getPrivateKey( this.priKeyPath );
      } catch (Exception e) {
      throw new RuntimeException(e.getMessage());
      }
      }

    }
    步骤三:修改AuthController,注入JwtProperties,并使用JwtUtils生成token
    package com.czxy.changgou4.controller;

    /**

    import com.czxy.changgou4.config.JwtProperties;
    import com.czxy.changgou4.domain.AuthUser;
    import com.czxy.changgou4.service.AuthService;
    import com.czxy.changgou4.utils.JwtUtils;
    import com.czxy.changgou4.vo.BaseResult;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;

    import javax.annotation.Resource;

    /**

    • Created by liangtong.
      */
      @RestController
      @RequestMapping(“/auth”)
      public class AuthController {

      @Resource
      private AuthService authService;

      @Resource
      private StringRedisTemplate stringRedisTemplate;

      @Resource
      private JwtProperties jwtProperties;

      @PostMapping(“/login”)
      public BaseResult login(@RequestBody AuthUser user){
      //校验验证码–使用后删除
      String redisCode = stringRedisTemplate.opsForValue().get( “login” + user.getUsername() );
      stringRedisTemplate.delete( “login” + user.getUsername() );
      if(redisCode == null) {
      return BaseResult.error(“验证码无效”);
      }
      if(! redisCode.equalsIgnoreCase(user.getCode())) {
      return BaseResult.error(“验证码错误”);
      }
      //登录
      AuthUser loginUser = authService.login(user);
      if(loginUser != null ) {
      //生成Token
      String token = JwtUtils.generateToken(loginUser, jwtProperties.getExpire(), jwtProperties.getPrivateKey());

           return BaseResult.ok("登录成功").append("loginUser",loginUser).append("token", token);
       } else {
           return BaseResult.error("用户名或密码不匹配");
       }
      

      }
      }

    4.9.2使用token
    步骤一:登录成功后保存token,修改 Login.vue页面

    async loginFn() {
      let { data } = await this.$request.login( this.user )
      if( data.code == 20000) {
        //成功
        sessionStorage.setItem('user' , JSON.stringify(data.other.loginUser) )
        //保存token
        sessionStorage.setItem('token' , data.other.token )
        //跳转到首页
        this.$router.push('/')
      } else {
        this.errorMsg = data.message
      }
    }
    

    步骤二:请求是自动携带token,修改apiclient.js,将token添加到请求头

    //参考 https://axios.nuxtjs.org/helpers
    let token = sessionStorage.getItem(‘token’)
    if( token ) {
    // Adds header: Authorization: 123 to all requests
    // this.$axios.setToken(‘123’)
    $axios.setToken( token )
    }

    步骤三:检查 nuxt.conf.js,插件模式改成“client”
    否则抛异常“sessionStorage is not defined”
    plugins: [
    { src: ‘~plugins/apiclient.js’, mode: ‘client’ }
    ],

    4.9.3校验token
    token的校验在网关项目处完成
    步骤一:修改application.yml添加jwt配置

    #自定义内容
    sc:
    jwt:
    secret: sc@Login(Auth}*^31)&czxy% # 登录校验的密钥
    pubKeyPath: D:/rsa/rsa.pub # 公钥地址
    priKeyPath: D:/rsa/rsa.pri # 私钥地址
    expire: 360 # 过期时间,单位分钟

    步骤二:创建 JwtProperties,用于加载配置文件

    package com.czxy.changgou4.config;

    import com.czxy.changgou4.utils.RsaUtils;
    import lombok.Data;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.stereotype.Component;

    import javax.annotation.PostConstruct;
    import java.io.File;
    import java.security.PrivateKey;
    import java.security.PublicKey;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @Data
      @ConfigurationProperties(prefix = “sc.jwt”)
      public class JwtProperties {

      private String secret; // 密钥

      private String pubKeyPath;// 公钥

      private String priKeyPath;// 私钥

      private int expire;// token过期时间

      private PublicKey publicKey; // 公钥

      private PrivateKey privateKey; // 私钥

      private static final Logger logger = LoggerFactory.getLogger(JwtProperties.class);

      @PostConstruct
      public void init(){
      try {
      File pubFile = new File(this.pubKeyPath);
      File priFile = new File(this.priKeyPath);
      if( !pubFile.exists() || !priFile.exists()){
      RsaUtils.generateKey( this.pubKeyPath ,this.priKeyPath , this.secret);
      }
      this.publicKey = RsaUtils.getPublicKey( this.pubKeyPath );
      this.privateKey = RsaUtils.getPrivateKey( this.priKeyPath );
      } catch (Exception e) {
      throw new RuntimeException(e.getMessage());
      }
      }

    }

    步骤三:编写过滤器,对所有路径进行拦截

    package com.czxy.changgou4.filter;

    import com.czxy.changgou4.config.FilterProperties;
    import com.czxy.changgou4.config.JwtProperties;
    import com.czxy.changgou4.pojo.User;
    import com.czxy.changgou4.utils.JwtUtils;
    import com.czxy.changgou4.utils.RsaUtils;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    import org.springframework.cloud.gateway.filter.GlobalFilter;
    import org.springframework.core.Ordered;
    import org.springframework.core.io.buffer.DataBuffer;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.server.reactive.ServerHttpRequest;
    import org.springframework.http.server.reactive.ServerHttpResponse;
    import org.springframework.stereotype.Component;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Flux;
    import reactor.core.publisher.Mono;

    import javax.annotation.Resource;
    import java.nio.charset.StandardCharsets;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @Component
      public class LoginFilter implements GlobalFilter, Ordered {

      @Resource
      private JwtProperties jwtProperties;

      @Override
      public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
      //1 获得请求路径
      ServerHttpRequest request = exchange.getRequest();
      String path = request.getURI().getPath();
      System.out.println(path);

       //2 白名单放行
      
      
       //3 获得token
       String token = request.getHeaders().getFirst("Authorization");
       //4 校验token
       try {
           JwtUtils.getObjectFromToken(token, RsaUtils.getPublicKey(jwtProperties.getPubKeyPath()), User.class);
           return chain.filter(exchange);
       } catch (Exception e) {
           ServerHttpResponse response = exchange.getResponse();
           response.setStatusCode(HttpStatus.UNAUTHORIZED);
           response.getHeaders().add("Content-Type","application/json;charset=UTF-8");
           DataBuffer wrap = response.bufferFactory().wrap("没有权限".getBytes(StandardCharsets.UTF_8));
           return exchange.getResponse().writeWith(Flux.just(wrap));
       }
      

      }

      @Override
      public int getOrder() {
      return 1;
      }
      }

    步骤四:修改前端 apiclient.js 文件,用于处理401异常

    //处理响应异常
    $axios.onError(error => {
    // token失效,服务器响应401
    if(error.response.status === 401) {
    console.error(error.response.data)
    redirect(‘/login’)
    }
    })

    api.js 完整代码

    var axios = null
    export default ({ $axios, redirect, process }, inject) => {

    //参考 https://axios.nuxtjs.org/helpers
    let token = sessionStorage.getItem(‘token’)
    if( token ) {
    // Adds header: Authorization: 123 to all requests
    // this.$axios.setToken(‘123’)
    $axios.setToken( token )
    }

    //处理响应异常
    $axios.onError(error => {
    // token失效,服务器响应401
    if(error.response.status === 401) {
    console.error(error.response.data)
    redirect(‘/login’)
    }
    })

    //赋值
    axios = $axios

    //4) 将自定义函数交于nuxt
    // 使用方式1:在vue中,this. r e q u e s t . x x x ( ) / / 使用方式 2 :在 n u x t 的 a s y n c D a t a 中, c o n t e n t . a p p . request.xxx() // 使用方式2:在nuxt的asyncData中,content.app. request.xxx()//使用方式2:在nuxtasyncData中,content.app.request.xxx()
    inject(‘request’, request)
    }

    4.9.4白名单
    不需要拦截的资源都配置到yml文件中,在过滤器直接放行
    步骤一:修改application.yml文件

    #自定义内容
    sc:
    jwt:
    secret: sc@Login(Auth}*^31)&czxy% # 登录校验的密钥
    pubKeyPath: D:/rsa/rsa.pub # 公钥地址
    priKeyPath: D:/rsa/rsa.pri # 私钥地址
    expire: 360 # 过期时间,单位分钟
    filter:
    allowPaths:
    - /checkusername
    - /checkmobile
    - /sms
    - /register
    - /login

    • /verifycode
      • /categorys
      • /news
      • /brands
      • /specifications
      • /search
      • /goods
      • /comments
    • swagger
    • /api-docs

    步骤二:创建FilterProperties配置文件,用于存放允许放行的路径

    package com.czxy.changgou4.config;

    import lombok.Data;
    import org.springframework.boot.context.properties.ConfigurationProperties;

    import java.util.List;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @Data
      @ConfigurationProperties(prefix=“sc.filter”)
      public class FilterProperties {

      //允许访问路径集合
      private List allowPaths;
      }

    步骤三:修改 LoginFilter,放行名单中配置的路径
    package com.czxy.changgou4.filter;

    import com.czxy.changgou4.config.FilterProperties;
    import com.czxy.changgou4.config.JwtProperties;
    import com.czxy.changgou4.pojo.User;
    import com.czxy.changgou4.utils.JwtUtils;
    import com.czxy.changgou4.utils.RsaUtils;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    import org.springframework.cloud.gateway.filter.GlobalFilter;
    import org.springframework.core.Ordered;
    import org.springframework.core.io.buffer.DataBuffer;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.server.reactive.ServerHttpRequest;
    import org.springframework.http.server.reactive.ServerHttpResponse;
    import org.springframework.stereotype.Component;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Flux;
    import reactor.core.publisher.Mono;

    import javax.annotation.Resource;
    import java.nio.charset.StandardCharsets;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @Component
      //2.1 加载JWT配置类
      @EnableConfigurationProperties({FilterProperties.class} ) //加载配置类
      public class LoginFilter implements GlobalFilter, Ordered {

      @Resource
      private FilterProperties filterProperties;

      @Resource
      private JwtProperties jwtProperties;

      @Override
      public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
      //1 获得请求路径
      ServerHttpRequest request = exchange.getRequest();
      String path = request.getURI().getPath();
      System.out.println(path);

       //2 白名单放行
       for (String allowPath  : filterProperties.getAllowPaths()) {
           //判断包含
           if(path.contains(allowPath)){
               return chain.filter(exchange);
           }
       }
      
      
      
       //3 获得token
       String token = request.getHeaders().getFirst("Authorization");
       //4 校验token
       try {
           JwtUtils.getObjectFromToken(token, RsaUtils.getPublicKey(jwtProperties.getPubKeyPath()), User.class);
           return chain.filter(exchange);
       } catch (Exception e) {
           ServerHttpResponse response = exchange.getResponse();
           response.setStatusCode(HttpStatus.UNAUTHORIZED);
           response.getHeaders().add("Content-Type","application/json;charset=UTF-8");
           DataBuffer wrap = response.bufferFactory().wrap("没有权限".getBytes(StandardCharsets.UTF_8));
           return exchange.getResponse().writeWith(Flux.just(wrap));
       }
      

      }

      @Override
      public int getOrder() {
      return 1;
      }
      }

    5.首页模块
    5.1构建首页
    步骤一:创建 ~/pages/index.vue页面

    步骤二:复制静态index.html页面到 index.vue中,并修改js和css引用方式

    步骤三:引入已有公共组件

    <!-- 头部 start -->
    <!-- 省略 html原有内容  -->
    <!-- 底部导航 end -->
    
    <div style="clear:both;"></div>
    <!-- 底部版权 start -->
    <Footer></Footer>
    <!-- 底部版权 end -->
    

    步骤四:将所有“头部”抽取到“HeaderSearch”组件
    1)修改图片路径

    步骤五:将所有“底部导航”抽取“BottomNav”组件

    步骤六:修改 ~/pages/index.vue,添加“HeaderSeach”和“BottomNav”组件

    <!-- 头部 start -->
    <HeaderSearch></HeaderSearch>
    <!-- 头部 end-->
    
    <div style="clear:both;"></div>
    
    <!-- 
    	省略 html原有内容 
    -->
    
    <div style="clear:both;"></div>
    
    <!-- 底部导航 start -->
    <BottomNav></BottomNav>
    <!-- 底部导航 end -->
    
    <div style="clear:both;"></div>
    <!-- 底部版权 start -->
    <Footer></Footer>
    <!-- 底部版权 end -->
    

    5.2快报
    5.2.1接口
    GET http://localhost:10010/web-service/news?pageNum=1&pageSize=5&sortWay=desc

    5.2.2后端实现:JavaBean

    步骤一:修改pojo项目,创建分页基类 PageRequest
    package com.czxy.changgou4.vo;

    import lombok.Data;

    /**

    • @author 桐叔
    • @email [email protected]
      */
      @Data
      public class PageRequest {
      private Integer pageNum; //当前页
      private Integer pageSize; //每页条数
      private Integer limit; //限制条数
      private Integer offset; //偏移
      private String sortBy; //排序字段
      private String sortWay; //排序方式(asc | desc)
      }

    步骤二:修改pojo项目,创建NewsVo类,继承PageRequest类
    NewsVo在PageRequest基础上,进行特殊定制。如果不需要任何增强,可以直接使用PageRequest
    package com.czxy.changgou4.vo;

    import lombok.Data;

    /**

    • @author 桐叔
    • @email [email protected]
      */
      @Data
      public class NewsVo extends PageRequest {
      }

    步骤三:根据数据库表创建News对象
    package com.czxy.changgou4.pojo;

    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;
    import lombok.Data;

    import java.util.Date;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @TableName(“tb_news”)
      @Data
      public class News {
      @TableId
      private Integer id;

      @TableField(value = “title”)
      private String title;

      @TableField(value = “content”)
      private String content;

      @TableField(value = “author”)
      private String author;

      @TableField(value = “created_at”)
      private Date createdAt;

      @TableField(value = “updated_at”)
      private Date updatedAt;

    }

    5.2.3后端实现:查询功能

    步骤一:创建mapper
    package com.czxy.changgou4.mapper;

    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.czxy.changgou4.pojo.News;
    import org.apache.ibatis.annotations.Mapper;

    /**

    • @author 桐叔
    • @email [email protected]
      */
      @Mapper
      public interface NewsMapper extends BaseMapper {
      }

    步骤二:创建service接口

    package com.czxy.changgou4.service;

    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.baomidou.mybatisplus.extension.service.IService;
    import com.czxy.changgou4.pojo.News;
    import com.czxy.changgou4.vo.NewsVo;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      public interface NewsService extends IService {

      /**

      • 查询所有新闻
      • @param newsVo
      • @return
        */
        public Page findAll(NewsVo newsVo);
        }

    步骤三:创建service实现类,按照创建时间,根据排序方式进行排序

    package com.czxy.changgou4.service.impl;

    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.czxy.changgou4.mapper.NewsMapper;
    import com.czxy.changgou4.pojo.News;
    import com.czxy.changgou4.service.NewsService;
    import com.czxy.changgou4.vo.NewsVo;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @Service
      @Transactional
      public class NewsServiceImpl extends ServiceImpl<NewsMapper, News> implements NewsService {
      @Override
      public Page findAll(NewsVo newsVo) {
      //1 条件
      QueryWrapper queryWrapper = new QueryWrapper();
      if(“asc”.equals(newsVo.getSortWay())) {
      queryWrapper.orderByAsc(“created_at”);
      } else {
      queryWrapper.orderByDesc(“created_at”);
      }

       //2 分页
       Page<News> page = new Page<>(newsVo.getPageNum(), newsVo.getPageSize());
      
       //3 查询
       baseMapper.selectPage(page, queryWrapper);
      
       return page;
      

      }
      }

    步骤四:修改controller

    package com.czxy.changgou4.controller;

    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.czxy.changgou4.pojo.News;
    import com.czxy.changgou4.service.NewsService;
    import com.czxy.changgou4.vo.BaseResult;
    import com.czxy.changgou4.vo.NewsVo;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;

    import javax.annotation.Resource;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @RestController
      @RequestMapping(“/news”)
      public class NewsController {

      @Resource
      private NewsService newsService;

      @GetMapping
      public BaseResult findAll(NewsVo newsVo){
      //1 查询
      Page page = this.newsService.findAll( newsVo );
      //2 封装
      return BaseResult.ok(“成功”, page);
      }

    }

    5.2.4前端实现
    步骤一:编写服务端apiserver.js

    步骤二:编写服务端apiserver.js,修改nuxt.config.js 配置仅服务器可用

    plugins: [
    { src: ‘~plugins/apiclient.js’,mode: ‘client’},
    { src: ‘~plugins/apiserver.js’},
    ],

    步骤三:修改“apiserver.js”,查询快报
    const request = {
    //快报
    findNews : () => {
    return axios.get(‘/web-service/news’ , {
    params : {
    pageNum : 1,
    pageSize : 8 ,
    sortWay : ‘asc’
    }
    })
    }
    }

    var axios = null
    export default ({ $axios, redirect, process }, inject) => {

    //赋值
    axios = $axios

    //4) 将自定义函数交于nuxt
    // 使用方式1:在vue中,this. r e q u e s t . x x x ( ) / / 使用方式 2 :在 n u x t 的 a s y n c D a t a 中, c o n t e n t . a p p . request.xxx() // 使用方式2:在nuxt的asyncData中,content.app. request.xxx()//使用方式2:在nuxtasyncData中,content.app.requestServer.xxx()
    inject(‘requestServer’, request)
    }

    步骤四:修改首页 index.vue,服务端查询快报

    async asyncData( { app } ) {
    let { data } = await app.$requestServer.findNews()

    return {
      newsList : data.data.records
    }
    

    },

    步骤五:遍历数据

            <li v-for="(n,index) in newsList" :title="n.title" :key="index" :class="{'odd': index%2==0 }">
    

    { {n.title}}
                
    步骤六:优化,超过的字符串显示“…”

    5.3分类
    5.3.1接口
    GET http://localhost:10010/web-service/categorys
    {
    “code”: 1,
    “message”: “查询成功”,
    “data”: [
    {
    “id”: 1,
    “children”: [
    {
    “id”: 2,
    “children”: [
    {
    “id”: 3,
    “children”: [],
    “cat_name”: “电子书”,
    “parent_id”: 2,
    “is_parent”: false
    },
    {
    “id”: 4,
    “children”: [],
    “cat_name”: “网络原创”,
    “parent_id”: 2,
    “is_parent”: false
    }
    ],
    “cat_name”: “电子书刊”,
    “parent_id”: 1,
    “is_parent”: true
    }
    ],
    “cat_name”: “图书、音像、电子书刊”,
    “parent_id”: 0,
    “is_parent”: true
    }
    ],
    “other”: {}
    }

    5.3.2后端实现:JavaBean
    package com.czxy.changgou4.pojo;

    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;
    import com.fasterxml.jackson.annotation.JsonInclude;
    import com.fasterxml.jackson.annotation.JsonProperty;
    import lombok.Data;

    import java.util.ArrayList;
    import java.util.List;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @TableName(“tb_category”)
      @Data
      public class Category {

      @TableId
      private Integer id;

      @TableField(value = “cat_name”)
      @JsonProperty(“cat_name”)
      private String catName;

      @TableField(value = “parent_id”)
      @JsonProperty(“parent_id”)
      private Integer parentId;

      @TableField(value = “is_parent”)
      @JsonProperty(“is_parent”)
      private Boolean isParent;

      //当前分类具有的所有孩子
      @TableField(exist = false)
      @JsonInclude(JsonInclude.Include.NON_EMPTY)
      private List children = new ArrayList<>();

    }

    5.3.3后端实现:查询

    步骤一:创建mapper
    package com.czxy.changgou4.mapper;

    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.czxy.changgou4.pojo.Category;
    import org.apache.ibatis.annotations.Mapper;

    /**

    • @author 桐叔
    • @email [email protected]
      */
      @Mapper
      public interface CategoryMapper extends BaseMapper {
      }

    步骤二:创建service接口, 添加查询所有方法
    package com.czxy.changgou4.service;

    import com.baomidou.mybatisplus.extension.service.IService;
    import com.czxy.changgou4.pojo.Category;

    import java.util.List;

    /**

    • @author 桐叔
    • @email [email protected]
      /
      public interface CategoryService extends IService {
      /
      *
      • 查询所有一级分类,每一个分类中含子分类
      • @return
        */
        public List findAll();
        }

    步骤三:创建service实现类,按照parentId升序排序,并将所有的分类进行按照级别进行处理。

    package com.czxy.changgou4.service.impl;

    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.czxy.changgou4.mapper.CategoryMapper;
    import com.czxy.changgou4.pojo.Category;
    import com.czxy.changgou4.service.CategoryService;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @Service
      @Transactional
      public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
      @Override
      public List findAll() {
      //1 条件
      QueryWrapper queryWrapper = new QueryWrapper<>();
      queryWrapper.orderByAsc(“parent_id”);

       //2 查询所有
       List<Category> findList = baseMapper.selectList(queryWrapper);
      
       //2 存放所有的一级分类
       List<Category> list = new ArrayList<>();
       // 3.1 存放每一个分类(此分类将来要做父分类的)
       Map<Integer , Category> cache = new HashMap<>();
       for (Category category : findList) {
           //2.1 过滤所有一级 parentId==0
           if(category.getParentId() == 0){
               list.add( category );
           }
      
           //3.2 向map存一份
           cache.put( category.getId() , category );
      
           //3.3 获得当前父分类
           Category parentCategory = cache.get( category.getParentId() );
           if( parentCategory != null) {
               parentCategory.getChildren().add( category );
           }
      
       }
      
       //2.2 返回处理结果
       return list;
      

      }
      }

    步骤四:创建controller

    package com.czxy.changgou4.controller;

    import com.czxy.changgou4.pojo.Category;
    import com.czxy.changgou4.service.CategoryService;
    import com.czxy.changgou4.vo.BaseResult;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;

    import javax.annotation.Resource;
    import java.util.List;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @RestController
      @RequestMapping(“/categorys”)
      public class CategoryController {

      @Resource
      private CategoryService categoryService;

      @GetMapping
      public BaseResult findAll(){
      //1 查询
      List list = categoryService.findAll();
      //2 封装
      return BaseResult.ok(“查询成功” , list );
      }

    }

    5.3.4前端实现
    步骤一:修改“apiserver.js”,查询所有分类

    //查询所有分类
    findCategorys : () => {
    return axios.get(“/web-service/categorys”)
    }

    步骤二:修改index.vue,使用asyncData查询分类
    asyncData函数只能在 pages 目录下使用

    async asyncData( { app } ) {
    // 查询快报 + 查询分类
    let [{data:newsData}, {data: categoryData}] = await Promise.all([
    app. r e q u e s t S e r v e r . f i n d N e w s ( ) , a p p . requestServer.findNews(), app. requestServer.findNews(),app.requestServer.findCategorys()
    ])
    return {
    newsList : newsData.data.records,
    categorysList : categoryData.data
    }

    },

    步骤三:修改index.vue,将查询的结果,通过属性传递给组件

    <!-- 头部 start -->
    <HeaderSearch :list="categorysList"></HeaderSearch>
    <!-- 头部 end-->
    

    步骤四:修改组件,声明list属性

    export default {
    props: {
    isFirst: { //是否是首页
    type: Boolean,
    default: true
    },
    list: { //数据
    type: Array
    }

    }
    }

    步骤五:遍历数据

    6.搜索模块
    6.1构建列表页
    步骤一:创建 ~/pages/list/_cid.vue 页面

    步骤二:复制静态list.html页面到 list/_cid.vue中,并修改js和css引用方式
    1)修改页面,完善标签

    2)替换图片路径

    <!-- 
    	省略 html原有内容 
    -->
    
    <!-- 底部版权 end -->
    

    步骤三:添加已有组件

    <div style="clear:both;"></div>
    
    <!-- 头部 start -->
    <HeaderSearch></HeaderSearch>
    <!-- 头部 end-->
    
    <div style="clear:both;"></div>
    
    <!-- 列表主体 start -->
    <!-- 省略 html原有内容 -->
    <!-- 列表主体 end-->
    
    <div style="clear:both;"></div>
    <!-- 底部导航 start -->
    <BottomNav></BottomNav>
    <!-- 底部导航 end -->
    
    <div style="clear:both;"></div>
    <!-- 底部版权 start -->
    <Footer></Footer>
    <!-- 底部版权 end -->
    

    6.2优化组件HeaderSearch组件
    非首页“HeaderSearch”组件,不能隐藏分类

    步骤一:修改HeaderSearch组件,添加props
    export default {
    props: {
    isFirst: { //是否是首页
    type: Boolean,
    default: true
    }
    }
    }

    步骤二:如果不在首页时,在指定的3处添加样式

    步骤三:修改 ~/pages/list/_id.vue ,将HeaderSearch组件的isFirst属性修改成false

    步骤四:显示分类数据

    <HeaderSearch :isFirst="false" :list="categorysList"></HeaderSearch>
    <!-- 头部 end-->
    

    async asyncData( { app } ) {
    // 查询快报 + 查询分类
    let [{data: categoryData}] = await Promise.all([
    app.$requestServer.findCategorys()
    ])
    return {
    categorysList : categoryData.data
    }
    }

    步骤五:修改logo访问路径

    6.3指定分类的所有品牌
    6.3.1接口
    GET http://localhost:10010/web-service/brands/category/76

    6.3.2后端实现:JavaBean
    表结构分析:
    分类表:tb_category
    品牌表:tb_brand
    关系:多对多(不同的分类,拥有不同的品牌)tb_category_brand

    package com.czxy.changgou4.pojo;

    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;
    import lombok.Data;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @Data
      @TableName(“tb_brand”)
      public class Brand {
      @TableId(type = IdType.AUTO)
      private Integer id; //品牌id

      @TableField(value = “brand_name”)
      private String brandName; //品牌名称

      @TableField(value = “logo”)
      private String logo; //品牌图片地址

    }

    6.3.3后端实现:查询

    步骤一:创建BrandMapper接口,根据分类查询所有的品牌

    package com.czxy.changgou4.mapper;

    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.czxy.changgou4.pojo.Brand;
    import org.apache.ibatis.annotations.*;

    import java.util.List;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @Mapper
      public interface BrandMapper extends BaseMapper {

      /**

      • 根据分类查询所有的品牌
      • @param categoryId
      • @return
        /
        @Select("select b.
        from tb_brand b ,tb_category_brand cb " +
        " where b.id = cb.brand_id and cb.category_id = #{categoryId}")

      public List findAll(@Param(“categoryId”) Integer categoryId);

    }

    步骤二:创建BrandService接口

    package com.czxy.changgou4.service;

    import com.baomidou.mybatisplus.extension.service.IService;
    import com.czxy.changgou4.pojo.Brand;

    import java.util.List;

    /**

    • @author 桐叔
    • @email [email protected]
      /
      public interface BrandService extends IService {
      /
      *
      • 根据分类查询所有的品牌
      • @param categoryId
      • @return
        */
        public List findAll(Integer categoryId);
        }

    步骤三:编写BrandService实现类

    package com.czxy.changgou4.service.impl;

    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.czxy.changgou4.mapper.BrandMapper;
    import com.czxy.changgou4.pojo.Brand;
    import com.czxy.changgou4.service.BrandService;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import java.util.List;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @Service
      @Transactional
      public class BrandServiceImpl extends ServiceImpl<BrandMapper, Brand> implements BrandService {

      @Override
      public List findAll(Integer categoryId) {
      return baseMapper.findAll(categoryId);
      }
      }

    步骤四:创建BrandController,符合接口规范
    package com.czxy.changgou4.controller;

    import com.czxy.changgou4.pojo.Brand;
    import com.czxy.changgou4.service.BrandService;
    import com.czxy.changgou4.vo.BaseResult;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;

    import javax.annotation.Resource;
    import java.util.List;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @RestController
      @RequestMapping(“/brands”)
      public class BrandController {

      @Resource
      private BrandService brandService;

      /**

      • //GET /brands/category/76
      • @param categoryId
      • @return
        */
        @GetMapping(“/category/{categoryId}”)
        public BaseResult findAll(@PathVariable(“categoryId”) Integer categoryId){
        //1 查询
        List list = this.brandService.findAll( categoryId );
        //2 封装
        return BaseResult.ok(“查询成功”, list );
        }

    }

    6.3.4前端实现
    步骤一:修改apiclient.js,编写ajax

    //指定分类的品牌
    findBrand : (categoryId) => {
    return axios.get(‘/web-service/brands/category/’ + categoryId)
    }

    步骤二:修改 ~/pages/list/_cid 页面,页面加载成功后,获得分类id

    data() {
    return {
    searchMap: { //搜索条件对象
    catId: ‘’ //分类id
    }
    }
    },
    async mounted() {
    //设置id
    this.searchMap.catId = this.$route.params.cid

    },

    步骤三:修改 ~/pages/list/_cid.vue 页面,查询品牌

    data() {
    return {
    searchMap: { //搜索条件对象
    catId: ‘’ //分类id
    },
    brandList : [], //所有品牌

    }
    

    },
    methods: {
    async findAllBrandFn() {
    let { data : brandData } = await this.$request.findBrand( this.searchMap.catId )
    this.brandList = brandData.data
    }
    },
    async mounted() {

    //设置id
    this.searchMap.catId = this.$route.params.cid
    // 查询所有的品牌
    this.findAllBrandFn()
    

    },

    步骤四:修改 ~/pages/list/_id 页面,显示分类数据

    品牌:

    步骤五:修改 ~/pages/list/_id 页面,回显选中数据

    methods: {
    brandSearch (bid){
    //记录品牌id
    this.searchMap.brandId = bid;
    //查询
    this.searchList();
    },
    searchList () {
    console.info(this.searchMap)
    }
    },

    6.4指定分类的所有规格
    6.4.1接口
    GET http://localhost:10010/web-service/specifications/category/76

    6.4.2后端实现:JavaBean
    表结构分析:
    规格表:tb_specification
    规格选项表:tb_specification_option
    关系:一对多(一个规格,拥有多个规格选项)

    规格选项
    package com.czxy.changgou4.pojo;

    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;
    import com.fasterxml.jackson.annotation.JsonProperty;
    import lombok.Data;

    /**

    • @author 桐叔

    • @email [email protected]
      /
      @TableName(“tb_specification_option”)
      @Data
      public class SpecificationOption {
      /

      CREATE TABLE tb_specification_option (
      id int(10) unsigned NOT NULL AUTO_INCREMENT,
      spec_id int(10) unsigned NOT NULL COMMENT ‘规格ID’,
      option_name varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT ‘选项名称’,
      PRIMARY KEY (id),
      KEY specification_options_spec_id_index (spec_id)
      )
      */
      @TableId(type = IdType.AUTO)
      private Integer id;

      @TableField(value=“spec_id”)
      @JsonProperty(“spec_id”)
      private Integer specId; //外键,规格ID

      private Specification specification; //外键对应对象

      @TableField(value=“option_name”)
      @JsonProperty(“option_name”)
      private String optionName; //选项名称

    }

    规格
    package com.czxy.changgou4.pojo;

    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;
    import com.fasterxml.jackson.annotation.JsonProperty;
    import lombok.Data;

    import java.util.List;

    /**

    • @author 桐叔

    • @email [email protected]
      /
      @TableName(“tb_specification”)
      @Data
      public class Specification {
      /

      CREATE TABLE tb_specification (
      id int(10) unsigned NOT NULL AUTO_INCREMENT,
      spec_name varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT ‘规格名称’,
      category_id int(10) unsigned NOT NULL COMMENT ‘分类ID’,
      PRIMARY KEY (id)
      )
      */
      @TableId(type = IdType.AUTO)
      private Integer id;

      @TableField(value = “spec_name”)
      @JsonProperty(“spec_name”)
      private String specName; //规格名称

      @TableField(value = “category_id”)
      private Integer categoryId; //分类外键
      private Category category; //分类外键对应对象

      private List options; //一个规格,具有多个规格选项

    }

    6.4.3后端实现:查询
    查询规格的同时,查询对应的规格选项

    步骤一:创建 SpecificationOptionMapper,用于查询指定“规格”的所有“规格选项”

    package com.czxy.changgou4.mapper;

    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.czxy.changgou4.pojo.SpecificationOption;
    import org.apache.ibatis.annotations.*;

    import java.util.List;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @Mapper
      public interface SpecificationOptionMapper extends BaseMapper {

      /**

      • 查询指定规格的所有规格选项
      • @param spceId
      • @return
        */
        @Select(“select * from tb_specification_option where spec_id = #{specId}”)
        @Results({
        @Result(property = “id”,column = “id”),
        @Result(property = “specId”,column = “spec_id”),
        @Result(property = “optionName”,column = “option_name”),
        })
        public List findSpecOptionBySpecId(@Param(“specId”) Integer spceId);

    }

    步骤二:创建SpecificationMapper,查询指定分类的所有规格,含规格选项信息

    package com.czxy.changgou4.mapper;

    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.czxy.changgou4.pojo.Specification;
    import org.apache.ibatis.annotations.*;

    import java.util.List;

    /**

    • @author 桐叔
    • @email [email protected]
      /
      @Mapper
      public interface SpecificationMapper extends BaseMapper {
      /
      *
      • 查询指定分类的所有规格
      • @param categoryId
      • @return
        */
        @Select(“select * from tb_specification where category_id = #{categoryId}”)
        @Results({
        @Result(property = “id”, column = “id”),
        @Result(property = “specName”, column = “spec_name”),
        @Result(property = “categoryId”, column = “category_id”),
        @Result(property = “options”, many=@Many(select=“com.czxy.changgou4.mapper.SpecificationOptionMapper.findSpecOptionBySpecId”), column = “id”),
        })
        public List findSpecificationByCategoryId(@Param(“categoryId”) Integer categoryId);

    }

    步骤三:创建SpecificationService接口

    package com.czxy.changgou4.service;

    import com.baomidou.mybatisplus.extension.service.IService;
    import com.czxy.changgou4.pojo.Specification;

    import java.util.List;

    /**

    • @author 桐叔
    • @email [email protected]
      /
      public interface SpecificationService extends IService {
      /
      *
      • 查询指定分类的所有规格
      • @param categoryId
      • @return
        */
        public List findSpecificationByCategoryId(Integer categoryId);
        }

    步骤四:创建SpecificationService实现类

    package com.czxy.changgou4.service.impl;

    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.czxy.changgou4.mapper.SpecificationMapper;
    import com.czxy.changgou4.pojo.Specification;
    import com.czxy.changgou4.service.SpecificationService;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import java.util.List;

    /**

    • @author 桐叔
    • @email [email protected]
      */
      @Service
      @Transactional
      public class SpecificationServiceImpl extends ServiceImpl<SpecificationMapper, Specification> implements SpecificationService {
      @Override
      public List findSpecificationByCategoryId(Integer categoryId) {
      return baseMapper.findSpecificationByCategoryId(categoryId);
      }
      }

    步骤五:创建 SpecificationController
    package com.czxy.changgou4.controller;

    import com.czxy.changgou4.pojo.Specification;
    import com.czxy.changgou4.service.SpecificationService;
    import com.czxy.changgou4.vo.BaseResult;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;

    import javax.annotation.Resource;
    import java.util.List;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @RestController
      @RequestMapping(“/specifications”)
      public class SpecificationController {
      @Resource
      private SpecificationService specificationService;

      /**

      • GET /specifications/category/:catid
      • @param categoryId
      • @return
        */
        @GetMapping(“/category/{catId}”)
        public BaseResult findSpecificationByCategoryId(@PathVariable(“catId”) Integer categoryId){
        //1 查询
        List list = this.specificationService.findSpecificationByCategoryId(categoryId);
        //2 封装
        return BaseResult.ok(“查询成功”, list);
        }

    }

    6.4.4前端实现
    步骤一:修改 api.js ,查询规格

    //指定分类的规格
    findSpec : (categoryId) => {
    return axios.get(‘/web-service/specifications/category/’ + categoryId)
    },

    步骤二:修改 pages/list/_id ,页面加载成功,查询规格
    data() {
    return {
    searchMap: { //搜索条件对象
    catId: ‘’, //分类id
    brandId: ‘’, // 品牌id
    },
    brandList : [], //所有品牌
    specList : [], //所有规格

    }
    

    },
    methods: {
    async findAllBrandFn() {
    let { data : brandData } = await this.KaTeX parse error: Expected 'EOF', got '}' at position 85: …dData.data }̲, async fin…request.findSpec( this.searchMap.catId )
    this.specList = specData.data
    },
    brandSearch(bid) {
    //记录品牌id
    this.searchMap.brandId = bid;

      //查询
      this.searchList();
    },
    searchList () {
      console.info(this.searchMap)
    }
    

    },
    async mounted() {

    //设置id
    this.searchMap.catId = this.$route.params.cid
    // 查询所有的品牌
    this.findAllBrandFn()
    // 查询所有的规格
    this.findAllSpecFn()
    

    },

    步骤三:显示数据

            <dl v-for="(sl,sli) in specList" :key="sli">
              <dt>{
          
          {sl.spec_name}}:</dt>
              <dd :class="{'cur': sl.selectId == ''}"><a href="" @click.prevent="specSearch(sl ,'' , )">不限</a></dd>
              <dd :class="{'cur' : sl.selectId == op.id }" v-for="(op,opi) in sl.options" :key="opi">
                <a href="" @click.prevent="specSearch(sl ,op , )">{
          
          {op.option_name}}</a>
              </dd>
            </dl>
    

    步骤四:选中规格,整体内容回显

    specSearch (spec , option ) {
    
      //记录选中的id
      this.$set(spec,'selectId', option ? option.id : '' )
    
    }
    

    6.5前置技术:ElasticSearch
    参考《elasticsearch入门.doc》

    6.6电商概念:SKU和SPU
    SPU = Standard Product Unit (标准产品单位)
    SPU是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。
    通俗点讲,属性值、特性相同的商品就可以称为一个SPU。
    例如:
    iphone X就是一个SPU,与颜色、款式、套餐都无关。
    SKU=stock keeping unit(库存量单位)
    SKU即库存进出计量的单位, 可以是以件、盒、托盘等为单位。
    SKU是物理上不可分割的最小存货单元。
    例如:
    iPhone X 128G 黑色,就是一个SKU

    总结
    SPU:可以理解为一类商品的总称(一组产品特性的集合)
    SKU:可以称之为最小库存单位,也就是所能唯一确定一件商品的唯一标识。

    6.7流程分析
    sku查询功能:从数据库查询sku相关的所有信息
    数据同步:将数据库中的数据,同步到es中。()
    数据库查询搜索慢、es查询速度快
    同步时机:1. 定时器(理论)、2. 测试程序(实施)
    搜索服务:从elasticsearch中搜索数据,默认情况下es中没有数据,通过“数据同步”操作后,es有数据

    详细版流程

    6.8表结构分析
    商品相关表总览

    表间关系

    6.9查询所有SKU
    6.9.1接口
    GET http://localhost:10010/web-service/sku/esData

    6.9.2后端实现:JavaBean

    数据库相关JavaBean
    Spu类似分类
    Sku 商品详情
    SkuComment sku的评论
    ES相关JavaBean
    ESData 准备存放到es中的数据,是以上三种数据的组合数据。

    SPU

    package com.czxy.changgou4.pojo;

    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;
    import lombok.Data;

    import java.util.Date;

    /**

    • Created by liangtong.
      */
      @TableName(“tb_spu”)
      @Data
      public class Spu {

      @TableId(type = IdType.AUTO)
      private Integer id;

      //spu名字
      @TableField(value=“spu_name”)
      private String spuName;
      //spu副名称
      @TableField(value=“spu_subname”)
      private String spuSubname;
      //商品logo
      @TableField(value=“logo”)
      private String logo;
      //分类1Id
      @TableField(value=“cat1_id”)
      private Integer cat1Id;
      //分类2ID
      @TableField(value=“cat2_id”)
      private Integer cat2Id;
      //分类3Id
      @TableField(value=“cat3_id”)
      private Integer cat3Id;

      @TableField(value=“brand_id”)
      private Integer brandId;
      @TableField(exist = false)
      private Brand brand;
      //审核时间
      @TableField(value=“check_time”)
      private String checkTime;
      //审核状态 审核状态,0:未审核,1:已通过,2:未通过
      @TableField(value=“check_status”)
      private String checkStatus;
      //价格
      @TableField(value=“price”)
      private String price;
      //是否上架
      @TableField(value=“is_on_sale”)
      private Integer isOnSale;
      //上架时间
      @TableField(value=“on_sale_time”)
      private Date onSaleTime;
      //删除时间
      @TableField(value=“deleted_at”)
      private String deletedAt;

      @TableField(value=“weight”)
      private String weight;

      //商品描述
      @TableField(value=“description”)
      private String description;
      //规格与包装
      @TableField(value=“packages”)
      private String packages;
      //售后保障
      @TableField(value=“aftersale”)
      private String aftersale;
      //规格列表,json串
      @TableField(value=“spec_list”)
      private String specList;

      @TableField(value=“created_at”)
      private String createdAt;
      @TableField(value=“updated_at”)
      private String updatedAt;

    }

    Sku
    package com.czxy.changgou4.pojo;

    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;

    /**

    • Created by liangtong.
      */
      @TableName(“tb_sku”)
      @Data
      @NoArgsConstructor
      @AllArgsConstructor
      public class Sku {

      @TableId(type = IdType.AUTO)
      private Integer id;
      //库存量
      @TableField(value=“stock”)
      private Integer stock;

      @TableField(value=“spu_id”)
      private Integer spuId;
      @TableField(exist = false)
      private Spu spu;
      //sku名字
      @TableField(value=“sku_name”)
      private String skuName;

      @TableField(value=“images”)
      private String images;
      @TableField(value=“price”)
      private Double price;

      //1:1|2:6|6:22
      @TableField(value=“spec_info_id_list”)
      private String specInfoIdList;
      //规格列表码,格式:{“机身颜色”:“白色”,“内存”:“3GB”,“机身存储”:“16GB”}
      @TableField(value=“spec_info_id_txt”)
      private String specInfoIdTxt;

    }

    SkuComment

    package com.czxy.changgou4.pojo;

    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;

    import java.util.Date;

    /**

    • Created by liangtong.
      */
      @TableName(“tb_sku_comment”)
      @Data
      @NoArgsConstructor
      @AllArgsConstructor
      public class SkuComment {

      @TableId(type = IdType.AUTO)
      private Integer id;
      @TableField(value=“created_at”)
      private Date createdAt;
      @TableField(value=“updated_at”)
      private Date updatedAt;

      @TableField(value=“user_id”)
      private Integer userId;
      @TableField(exist = false)
      private User user;

      @TableField(value=“spu_id”)
      private Integer spuId;
      @TableField(exist = false)
      private Spu spu;

      @TableField(value=“sku_id”)
      private Integer skuId;
      @TableField(exist = false)
      private Sku sku;

      @TableField(value=“ratio”)
      private String ratio;

      @TableField(value=“spec_list”)
      private String specList;

      @TableField(value=“content”)
      private String content;
      @TableField(value=“star”)
      private Integer star;
      @TableField(value=“isshow”)
      private String isShow;

      @TableField(value=“sn”)
      private String sn;

    }

    SkuPhoto

    package com.czxy.changgou4.pojo;

    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;
    import com.fasterxml.jackson.annotation.JsonProperty;
    import lombok.Data;

    /**

    • Created by liangtong.
      */
      @TableName(“tb_sku_photo”)
      @Data
      public class SkuPhoto {

      @TableId(type = IdType.AUTO)
      private Integer id;
      //外键
      @TableField(value=“sku_id”)
      @JsonProperty(“sku_id”)
      private Integer skuId;
      @TableField(exist = false)
      private Sku sku;

      @TableField(value=“url”)
      private String url;

    }

    ESData
    package com.czxy.changgou4.vo;

    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;

    import java.util.Date;
    import java.util.Map;

    /**

    • Created by liangtong.
      */
      @Data
      @AllArgsConstructor
      @NoArgsConstructor
      public class ESData {

      private Integer id; // skuId
      private String logo;//图片地址
      private String skuName;//sku名字
      private String all; // 所有需要被搜索的信息,包含标题,分类,甚至品牌
      private Date onSaleTime;//上架时间
      //品牌编号
      private Integer brandId;
      //分类id
      private Integer catId;
      //规格列表
      private Map<String, Object> specs;// 可搜索的规格参数,key是参数名,值是参数值
      private Double price;// 价格
      private String spuName;
      private Integer stock;
      private String description;
      private String packages;//规格与包装
      private String aftersale;//售后保障
      private String midlogo;
      //评价数
      private Integer commentCount;
      // 销量
      private Integer sellerCount;

    }

    6.9.3后端实现:搭建环境
    步骤一:修改父项目的pom.xml,锁定fastjson的依赖

    <fastjson.version>1.2.9</fastjson.version>

    com.alibaba fastjson ${fastjson.version}

    步骤二:修改web服务,添加fastjson依赖

    com.alibaba fastjson

    步骤三:修改网关,确定 ESData路径

    6.9.4后端实现:查询

    SpuMapper:通过SpuId查询详情,含品牌信息

    package com.czxy.changgou4.mapper;

    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.czxy.changgou4.pojo.Spu;
    import org.apache.ibatis.annotations.*;

    /**

    • Created by liangtong.
      */
      @Mapper
      public interface SpuMapper extends BaseMapper {

      @Select(“select * from tb_spu where id = #{spuId}”)
      @Results({
      @Result(property = “spuName” , column = “spu_name”),
      @Result(property = “spuSubname” , column = “spu_subname”),
      @Result(property = “cat1Id” , column = “cat1_id”),
      @Result(property = “cat2Id” , column = “cat2_id”),
      @Result(property = “cat3Id” , column = “cat3_id”),
      @Result(property = “brandId” , column = “brand_id”),
      @Result(property = “checkTime” , column = “check_time”),
      @Result(property = “checkStatus” , column = “check_status”),
      @Result(property = “isOnSale” , column = “is_on_sale”),
      @Result(property = “onSaleTime” , column = “on_sale_time”),
      @Result(property = “deletedAt” , column = “deleted_at”),
      @Result(property = “specList” , column = “spec_list”),
      @Result(property = “onSaleTime” , column = “on_sale_time”),
      @Result(property = “createdAt” , column = “created_at”),
      @Result(property = “updatedAt” , column = “updated_at”),
      @Result(property=“brand”, column=“brand_id”,
      one=@One(
      select=“com.czxy.changgou4.mapper.BrandMapper.selectById”
      ))
      })
      public Spu findSpuById(@Param(“spuId”) Integer spuId);

    }

    SkuCommentMapper:查询评论数

    package com.czxy.changgou4.mapper;

    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.czxy.changgou4.pojo.SkuComment;
    import org.apache.ibatis.annotations.*;

    import java.util.List;

    /**

    • Created by liangtong.
      */
      @Mapper
      public interface SkuCommentMapper extends BaseMapper {

      /**

      • 查询评论数
      • @param spu_id
      • @return
        /
        @Select("select count(
        ) from tb_sku_comment where spu_id = #{spu_id}")
        public Integer findNumBySpuId(@Param(“spu_id”) Integer spu_id);

      }

    SkuMapper:查询所有sku,含对应的spu信息
    package com.czxy.changgou4.mapper;

    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.czxy.changgou4.pojo.Sku;
    import org.apache.ibatis.annotations.*;

    import java.util.List;

    /**

    • Created by liangtong.
      */
      @Mapper
      public interface SkuMapper extends BaseMapper {

      @Select(“select * from tb_sku”)
      @Results(id=“skuResult” , value={
      @Result(id=true,column=“id”,property=“id”),
      @Result(column=“stock”,property=“stock”),
      @Result(column=“spu_id”,property=“spuId”),
      @Result(column=“sku_name”,property=“skuName”),
      @Result(column=“spec_info_id_list”,property=“specInfoIdList”),
      @Result(column=“spec_info_id_txt”,property=“specInfoIdTxt”),
      @Result(column=“spu_id”,property=“spu”,
      one=@One(
      select=“com.czxy.changgou4.mapper.SpuMapper.findSpuById”
      ))
      })
      public List findAllSkus();

    }

    SkuPhotoMapper

    package com.czxy.changgou4.mapper;

    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.czxy.changgou4.pojo.SkuPhoto;
    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Result;
    import org.apache.ibatis.annotations.Results;
    import org.apache.ibatis.annotations.Select;

    import java.util.List;

    /**

    • Created by liangtong.
      */
      @Mapper
      public interface SkuPhotoMapper extends BaseMapper {

    }

    package com.czxy.changgou4.service;

    import com.baomidou.mybatisplus.extension.service.IService;
    import com.czxy.changgou4.pojo.Sku;
    import com.czxy.changgou4.vo.ESData;

    import java.util.List;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      public interface SkuService extends IService {

      /**
      *

      • @return
        */
        public List findESData();
        }

    package com.czxy.changgou4.service.impl;

    import com.alibaba.fastjson.JSON;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.czxy.changgou4.mapper.SkuCommentMapper;
    import com.czxy.changgou4.mapper.SkuMapper;
    import com.czxy.changgou4.pojo.Sku;
    import com.czxy.changgou4.service.SkuService;
    import com.czxy.changgou4.vo.ESData;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import javax.annotation.Resource;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @Service
      @Transactional
      public class SkuServiceImpl extends ServiceImpl<SkuMapper, Sku> implements SkuService {

      @Resource
      private SkuCommentMapper skuCommentMapper;

      @Override
      public List findESData() {
      //1 查询所有详情sku
      List skulist = baseMapper.findAllSkus();

       //2 将SKU 转换成 ESData
       List<ESData> esDataList = new ArrayList<>();
      
       for (Sku sku:skulist){
           ESData esData = new ESData();
           // id
           esData.setId(sku.getId());
           // 图片地址
           esData.setLogo(sku.getSpu().getLogo());
           // 商品名称
           esData.setSkuName(sku.getSkuName());
           // all  “华为xx {"机身颜色":"白色","内存":"3GB","机身存储":"16GB"} 荣耀 ”
           esData.setAll(sku.getSkuName()+"   " + sku.getSpecInfoIdTxt() + "   " +sku.getSpu().getBrand().getBrandName());
           // on_sale_time
           esData.setOnSaleTime(sku.getSpu().getOnSaleTime());
           // brand_id
           esData.setBrandId(sku.getSpu().getBrandId());
           // cat_id
           esData.setCatId(sku.getSpu().getCat3Id());
           //  Map<String, Object> specs;// 可搜索的规格参数,key是参数名,值是参数值
           Map<String,Object> specs = JSON.parseObject(sku.getSpecInfoIdTxt(), Map.class);
      

    // Map newSpecs = new HashMap();
    // for(String key : specs.keySet()){
    // newSpecs.put(“spec” + key , specs.get(key));
    // }

            esData.setSpecs(specs);
            // price 价格
            esData.setPrice(sku.getPrice());
            // spu_name
            esData.setSpuName(sku.getSpu().getSpuName());
            // stock 库存
            esData.setStock(sku.getStock());
            // description
            esData.setDescription(sku.getSpu().getDescription());
            // packages;//规格与包装
            esData.setPackages(sku.getSpu().getPackages());
            // aftersale;//售后保障
            esData.setAftersale(sku.getSpu().getAftersale());
            // midlogo;
            esData.setMidlogo(sku.getSpu().getLogo());
            // comment_count; 评价数
            Integer comment_count = skuCommentMapper.findNumBySpuId(sku.getSpu().getId());
            esData.setCommentCount(comment_count);
    
            //销售量
            esData.setSellerCount(10);
    
            esDataList.add(esData);
        }
    
        return esDataList;
    
    }
    

    }

    package com.czxy.changgou4.controller;

    import com.czxy.changgou4.service.SkuService;
    import com.czxy.changgou4.vo.BaseResult;
    import com.czxy.changgou4.vo.ESData;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;

    import java.util.List;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @RestController
      @RequestMapping(“/sku”)
      public class SkuController {

      @Autowired
      private SkuService skuService;

      @GetMapping(“/esData”)
      public BaseResult<List> findESData(){

       List<ESData> esData = skuService.findESData();
       return BaseResult.ok("查询成功", esData);
      

      }

    }

    6.10初始化数据
    6.10.1搭建环境:搜索服务
    步骤一:构建项目changgou4-service-search

    步骤二:修改pom.xml,添加依赖



    com.czxy.changgou
    changgou4_common


    com.czxy.changgou
    changgou4_pojo



    org.springframework.boot
    spring-boot-starter-web

    <!-- nacos 客户端 -->
    <dependency>
        <groupId>com.alibaba.nacos</groupId>
        <artifactId>nacos-client</artifactId>
    </dependency>
    
    <!-- nacos 服务发现 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    
    <!-- openfeign 远程调用 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    
    <!--swagger2-->
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger2</artifactId>
    </dependency>
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger-ui</artifactId>
    </dependency>
    
    <!--elasticsearch-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
    

    步骤三:创建yml文件

    server:
    port: 8090
    spring:
    application:
    name: search-service
    cloud:
    nacos:
    discovery:
    server-addr: 127.0.0.1:8848 #nacos服务地址

    拷贝配置类

    package com.czxy.changgou4.config;

    import org.elasticsearch.client.RestHighLevelClient;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.elasticsearch.client.ClientConfiguration;
    import org.springframework.data.elasticsearch.client.RestClients;
    import org.springframework.data.elasticsearch.config.AbstractElasticsearchConfiguration;
    import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @Configuration
      @EnableElasticsearchRepositories
      public class RestClientConfig extends AbstractElasticsearchConfiguration {

      @Override
      @Bean
      public RestHighLevelClient elasticsearchClient() {

       final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
               .connectedTo("localhost:9200")
               .build();
      
       return RestClients.create(clientConfiguration).rest();
      

      }
      }

    启动类
    package com.czxy.changgou4;

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.openfeign.EnableFeignClients;

    /**

    • @author 桐叔
    • @email [email protected]
      */
      @SpringBootApplication
      @EnableDiscoveryClient
      @EnableFeignClients
      public class CGSearchServiceApplication {
      public static void main(String[] args) {
      SpringApplication.run(CGSearchServiceApplication.class,args);
      }
      }

    6.10.2远程调用:查询所有

    步骤一:编写JavaBean,拷贝ESData,并添加elasticsearch相关注解
    package com.czxy.changgou4.vo;

    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import org.springframework.data.annotation.Id;
    import org.springframework.data.elasticsearch.annotations.Document;
    import org.springframework.data.elasticsearch.annotations.Field;
    import org.springframework.data.elasticsearch.annotations.FieldType;

    import java.util.Date;
    import java.util.Map;

    /**

    • Created by liangtong.
      */
      @Data
      @AllArgsConstructor
      @NoArgsConstructor
      @Document(indexName = “skus”, type = “docs” )
      public class SearchSku {
      @Id
      private Long id; // skuId
      @Field(type = FieldType.Text)
      private String logo;//图片地址
      @Field(type = FieldType.Text ,analyzer = “ik_max_word”)
      private String skuName;//sku名字
      @Field(type = FieldType.Text ,analyzer = “ik_max_word”)
      private String all; // 所有需要被搜索的信息,包含标题,分类,甚至品牌
      @Field(type = FieldType.Date)
      private Date onSaleTime;//商家时间
      //品牌编号
      @Field(type = FieldType.Integer)
      private Integer brandId;
      // 分类id
      @Field(type = FieldType.Integer)
      private Integer catId;

      //规格列表
      //@IndexDynamicSettings
      private Map<String, Object> specs;// 可搜索的规格参数,key是参数名,值是参数值
      @Field(type = FieldType.Double)
      private Double price;// 价格
      @Field(type = FieldType.Text)
      private String spuName;
      @Field(type = FieldType.Integer)
      private Integer stock;
      @Field(type = FieldType.Text)
      private String description;
      @Field(type = FieldType.Text)
      private String packages;//规格与包装
      @Field(type = FieldType.Text)
      private String aftersale;//售后保障
      @Field(type = FieldType.Text)
      private String midlogo;
      //评价数
      @Field(type = FieldType.Integer)
      private Integer commentCount;
      // 销量
      @Field(type = FieldType.Integer)
      private Integer sellerCount;

    }

    步骤二:远程调用

    package com.czxy.changgou4.feign;

    import com.czxy.changgou4.vo.BaseResult;
    import com.czxy.changgou4.vo.ESData;
    import com.czxy.changgou4.vo.SearchSku;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;

    import java.util.List;

    /**

    • Created by liangtong.
      */
      @FeignClient(value=“web-service”,path = “/sku”)
      public interface SkuFeign {

      @GetMapping(“/esData”)
      public BaseResult<List> findESData();

    }

    步骤三:测试类

    package com.czxy.changgou4;

    import com.czxy.changgou4.feign.SkuFeign;
    import com.czxy.changgou4.repository.SkuRepository;
    import com.czxy.changgou4.vo.BaseResult;
    import com.czxy.changgou4.vo.SearchSku;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
    import org.springframework.http.ResponseEntity;
    import org.springframework.test.context.junit4.SpringRunner;

    import javax.annotation.Resource;
    import java.util.List;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @RunWith(SpringRunner.class)
      @SpringBootTest(classes = CGSearchServiceApplication.class)
      public class TestSkuClient {
      @Resource
      private SkuFeign skuFeign;

      @Test
      public void findAllSkus() throws Exception {
      BaseResult<List> baseResult = skuFeign.findESData();
      List list = baseResult.getData();
      System.out.println(“总条数:” + list.size());
      for(SearchSku ss:list){
      System.out.println(ss);
      }
      }

    }

    6.10.3整合ElasticSearch
    步骤一:创建repository类

    package com.czxy.changgou4.repository;

    import com.czxy.changgou4.vo.SearchSku;
    import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

    /**

    • Created by liangtong.
      */
      public interface SkuRepository extends ElasticsearchRepository<SearchSku, Long> {
      }

    步骤二:测试创建索引
    @Resource
    private ElasticsearchRestTemplate elasticsearchRestTemplate;
    @Test
    public void createIndex(){
    // 创建索引
    this.elasticsearchRestTemplate.createIndex(SearchSku.class);
    // 配置映射
    this.elasticsearchRestTemplate.putMapping(SearchSku.class);
    }

    步骤三:测试装载数据
    @Resource
    private SkuRepository skuRepository;

    @Test
    public void loadData() throws Exception {
    BaseResult<List> baseResult = skuFeign.findESData();
    List list = baseResult.getData();
    for(SearchSku ss:list){
    ss.setAll(ss.toString());
    System.out.println(ss);
    }
    this.skuRepository.saveAll(list);
    }

    6.11基本查询
    6.11.1查询所有
    package com.czxy.changgou4;

    import com.czxy.changgou4.repository.SkuRepository;
    import com.czxy.changgou4.vo.SearchSku;
    import org.elasticsearch.index.query.BoolQueryBuilder;
    import org.elasticsearch.index.query.QueryBuilders;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.data.domain.Page;
    import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
    import org.springframework.test.context.junit4.SpringRunner;

    import javax.annotation.Resource;
    import java.util.List;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @RunWith(SpringRunner.class)
      @SpringBootTest(classes = CGSearchServiceApplication.class)
      public class TestSkuRepository {
      @Resource
      private SkuRepository skuRepository;

      @Test
      public void testFind() {
      //1 简单条件
      BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

       //2 复合条件
       NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
       queryBuilder.withQuery(boolQueryBuilder);
      
       //3 查询
       Page<SearchSku> pageInfo = this.skuRepository.search(queryBuilder.build());
      
       // 总条数
       System.out.println(pageInfo.getTotalElements());
      
       // 查询结果
       pageInfo.getContent().forEach(System.out::println);
      

      }

    }

    6.11.2根据分类查询
    package com.czxy.changgou4;

    import com.czxy.changgou4.repository.SkuRepository;
    import com.czxy.changgou4.vo.SearchSku;
    import org.elasticsearch.index.query.BoolQueryBuilder;
    import org.elasticsearch.index.query.QueryBuilders;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.data.domain.Page;
    import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
    import org.springframework.test.context.junit4.SpringRunner;

    import javax.annotation.Resource;
    import java.util.List;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @RunWith(SpringRunner.class)
      @SpringBootTest(classes = CGSearchServiceApplication.class)
      public class TestSkuRepository {
      @Resource
      private SkuRepository skuRepository;

      @Test
      public void testFind() {
      //1 简单条件
      BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

       // 1.1 根据分类查询
       boolQueryBuilder.must(QueryBuilders.termQuery("catId",76));
      
      
       //2 复合条件
       NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
       queryBuilder.withQuery(boolQueryBuilder);
      
       //3 查询
       Page<SearchSku> pageInfo = this.skuRepository.search(queryBuilder.build());
      
       // 总条数
       System.out.println(pageInfo.getTotalElements());
      
       // 查询结果
       pageInfo.getContent().forEach(System.out::println);
      

      }

    }

    6.11.3根据品牌查询
    package com.czxy.changgou4;

    import com.czxy.changgou4.repository.SkuRepository;
    import com.czxy.changgou4.vo.SearchSku;
    import org.elasticsearch.index.query.BoolQueryBuilder;
    import org.elasticsearch.index.query.QueryBuilders;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.data.domain.Page;
    import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
    import org.springframework.test.context.junit4.SpringRunner;

    import javax.annotation.Resource;
    import java.util.List;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @RunWith(SpringRunner.class)
      @SpringBootTest(classes = CGSearchServiceApplication.class)
      public class TestSkuRepository {
      @Resource
      private SkuRepository skuRepository;

      @Test
      public void testFind() {
      //1 简单条件
      BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

       // 1.1 根据分类查询
      

    // boolQueryBuilder.must(QueryBuilders.termQuery(“catId”,76));

        // 1.2 根据品牌查询
        boolQueryBuilder.must(QueryBuilders.termQuery("brandId",15127));
    
        //2 复合条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        queryBuilder.withQuery(boolQueryBuilder);
    
        //3 查询
        Page<SearchSku> pageInfo = this.skuRepository.search(queryBuilder.build());
    
        // 总条数
        System.out.println(pageInfo.getTotalElements());
    
        // 查询结果
        pageInfo.getContent().forEach(System.out::println);
    
    }
    

    }

    6.11.4根据指定规格进行查询
    package com.czxy.changgou4;

    import com.czxy.changgou4.repository.SkuRepository;
    import com.czxy.changgou4.vo.SearchSku;
    import org.elasticsearch.index.query.BoolQueryBuilder;
    import org.elasticsearch.index.query.QueryBuilders;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.data.domain.Page;
    import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
    import org.springframework.test.context.junit4.SpringRunner;

    import javax.annotation.Resource;
    import java.util.List;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @RunWith(SpringRunner.class)
      @SpringBootTest(classes = CGSearchServiceApplication.class)
      public class TestSkuRepository {
      @Resource
      private SkuRepository skuRepository;

      @Test
      public void testFind() {
      //1 简单条件
      BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

       // 1.1 根据分类查询
      

    // boolQueryBuilder.must(QueryBuilders.termQuery(“catId”,76));

        // 1.2 根据品牌查询
    

    // boolQueryBuilder.must(QueryBuilders.termQuery(“brandId”,15127));

        // 1.3 根据规格进行查询 机身颜色=灰色
        boolQueryBuilder.must(QueryBuilders.termQuery( "specs.机身颜色.keyword", "灰色"));
    
        //2 复合条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        queryBuilder.withQuery(boolQueryBuilder);
    
        //3 查询
        Page<SearchSku> pageInfo = this.skuRepository.search(queryBuilder.build());
    
        // 总条数
        System.out.println(pageInfo.getTotalElements());
    
        // 查询结果
        pageInfo.getContent().forEach(System.out::println);
    
    }
    

    }

    6.11.5价格区间查询
    package com.czxy.changgou4;

    import com.czxy.changgou4.repository.SkuRepository;
    import com.czxy.changgou4.vo.SearchSku;
    import org.elasticsearch.index.query.BoolQueryBuilder;
    import org.elasticsearch.index.query.QueryBuilders;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.data.domain.Page;
    import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
    import org.springframework.test.context.junit4.SpringRunner;

    import javax.annotation.Resource;
    import java.util.List;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @RunWith(SpringRunner.class)
      @SpringBootTest(classes = CGSearchServiceApplication.class)
      public class TestSkuRepository {
      @Resource
      private SkuRepository skuRepository;

      @Test
      public void testFind() {
      //1 简单条件
      BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

       // 1.1 根据分类查询
      

    // boolQueryBuilder.must(QueryBuilders.termQuery(“catId”,76));

        // 1.2 根据品牌查询
    

    // boolQueryBuilder.must(QueryBuilders.termQuery(“brandId”,15127));

        // 1.3 根据规格进行查询 机身颜色=灰色
    

    // boolQueryBuilder.must(QueryBuilders.termQuery( “specs.机身颜色.keyword”, “灰色”));

        // 1.4 范围查询:
        boolQueryBuilder.must(QueryBuilders.rangeQuery("price").gte(100000).lt(140000));
    
    
        //2 复合条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        queryBuilder.withQuery(boolQueryBuilder);
    
        //3 查询
        Page<SearchSku> pageInfo = this.skuRepository.search(queryBuilder.build());
    
        // 总条数
        System.out.println(pageInfo.getTotalElements());
    
        // 查询结果
        pageInfo.getContent().forEach(System.out::println);
    
    }
    

    }

    6.11.6关键字查询
    matchQuery 首先会解析查询字符串,进行分词,然后查询
    term query 不分词,直接查询

    package com.czxy.changgou4;

    import com.czxy.changgou4.repository.SkuRepository;
    import com.czxy.changgou4.vo.SearchSku;
    import org.elasticsearch.index.query.BoolQueryBuilder;
    import org.elasticsearch.index.query.QueryBuilders;
    import org.elasticsearch.search.sort.SortBuilders;
    import org.elasticsearch.search.sort.SortOrder;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.data.domain.Page;
    import org.springframework.data.domain.PageRequest;
    import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
    import org.springframework.test.context.junit4.SpringRunner;

    import javax.annotation.Resource;
    import java.util.List;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @RunWith(SpringRunner.class)
      @SpringBootTest(classes = CGSearchServiceApplication.class)
      public class TestSkuRepository {
      @Resource
      private SkuRepository skuRepository;

      @Test
      public void testFind() {
      //1 简单条件
      BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

       // 1.1 根据分类查询
      

    // boolQueryBuilder.must(QueryBuilders.termQuery(“catId”,76));

        // 1.2 根据品牌查询
    

    // boolQueryBuilder.must(QueryBuilders.termQuery(“brandId”,15127));

        // 1.3 根据规格进行查询 机身颜色=灰色
    

    // boolQueryBuilder.must(QueryBuilders.termQuery( “specs.机身颜色.keyword”, “灰色”));

        // 1.4 范围查询:
    

    // boolQueryBuilder.must(QueryBuilders.rangeQuery(“price”).gte(100000).lt(140000));

        // 1.5 关键字查询
        boolQueryBuilder.must(QueryBuilders.matchQuery("skuName","华为"));
    
    
        //2 复合条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        queryBuilder.withQuery(boolQueryBuilder);
    
        //3 查询
        Page<SearchSku> pageInfo = this.skuRepository.search(queryBuilder.build());
    
        // 总条数
        System.out.println(pageInfo.getTotalElements());
    
        // 查询结果
        pageInfo.getContent().forEach(System.out::println);
    
    }
    

    }

    6.11.7分页
    package com.czxy.changgou4;

    import com.czxy.changgou4.repository.SkuRepository;
    import com.czxy.changgou4.vo.SearchSku;
    import org.elasticsearch.index.query.BoolQueryBuilder;
    import org.elasticsearch.index.query.QueryBuilders;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.data.domain.Page;
    import org.springframework.data.domain.PageRequest;
    import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
    import org.springframework.test.context.junit4.SpringRunner;

    import javax.annotation.Resource;
    import java.util.List;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @RunWith(SpringRunner.class)
      @SpringBootTest(classes = CGSearchServiceApplication.class)
      public class TestSkuRepository {
      @Resource
      private SkuRepository skuRepository;

      @Test
      public void testFind() {
      //1 简单条件
      BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

       // 1.1 根据分类查询
      

    // boolQueryBuilder.must(QueryBuilders.termQuery(“catId”,76));

        // 1.2 根据品牌查询
    

    // boolQueryBuilder.must(QueryBuilders.termQuery(“brandId”,15127));

        // 1.3 根据规格进行查询 机身颜色=灰色
    

    // boolQueryBuilder.must(QueryBuilders.termQuery( “specs.机身颜色.keyword”, “灰色”));

        // 1.4 范围查询:
    

    // boolQueryBuilder.must(QueryBuilders.rangeQuery(“price”).gte(100000).lt(140000));

        //2 复合条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        queryBuilder.withQuery(boolQueryBuilder);
    
        //2.1 分页查询
        // 第一页 : 从0开始
        queryBuilder.withPageable(PageRequest.of(0  ,6));
    
    
        //3 查询
        Page<SearchSku> pageInfo = this.skuRepository.search(queryBuilder.build());
    
        // 总条数
        System.out.println(pageInfo.getTotalElements());
    
        // 查询结果
        pageInfo.getContent().forEach(System.out::println);
    
    }
    

    }

    6.11.8排序
    package com.czxy.changgou4;

    import com.czxy.changgou4.repository.SkuRepository;
    import com.czxy.changgou4.vo.SearchSku;
    import org.elasticsearch.index.query.BoolQueryBuilder;
    import org.elasticsearch.index.query.QueryBuilders;
    import org.elasticsearch.search.sort.SortBuilders;
    import org.elasticsearch.search.sort.SortOrder;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.data.domain.Page;
    import org.springframework.data.domain.PageRequest;
    import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
    import org.springframework.test.context.junit4.SpringRunner;

    import javax.annotation.Resource;
    import java.util.List;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @RunWith(SpringRunner.class)
      @SpringBootTest(classes = CGSearchServiceApplication.class)
      public class TestSkuRepository {
      @Resource
      private SkuRepository skuRepository;

      @Test
      public void testFind() {
      //1 简单条件
      BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

       // 1.1 根据分类查询
      

    // boolQueryBuilder.must(QueryBuilders.termQuery(“catId”,76));

        // 1.2 根据品牌查询
    

    // boolQueryBuilder.must(QueryBuilders.termQuery(“brandId”,15127));

        // 1.3 根据规格进行查询 机身颜色=灰色
    

    // boolQueryBuilder.must(QueryBuilders.termQuery( “specs.机身颜色.keyword”, “灰色”));

        // 1.4 范围查询:
    

    // boolQueryBuilder.must(QueryBuilders.rangeQuery(“price”).gte(100000).lt(140000));

        //2 复合条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        queryBuilder.withQuery(boolQueryBuilder);
    
        //2.1 分页查询
        // 第一页 : 从0开始
    

    // queryBuilder.withPageable(PageRequest.of(0 ,6));

        //2.2 排序
        queryBuilder.withSort(SortBuilders.fieldSort("price").order(SortOrder.ASC));
    
        //3 查询
        Page<SearchSku> pageInfo = this.skuRepository.search(queryBuilder.build());
    
        // 总条数
        System.out.println(pageInfo.getTotalElements());
    
        // 查询结果
        pageInfo.getContent().forEach(System.out::println);
    
    }
    

    }

    6.12后端实现:商品搜索
    6.12.1分析

    6.12.2接口
    POST http://localhost:10010/search-service/sku/search
    {
    “catId”: 76,
    “current”: 1,
    “size”: 5,
    “keyword”: “64GB”,
    “brandId”: “8557”,
    “specList”: {
    “机身颜色”: “金色”
    },
    “minPrice”: “100000”,
    “maxPrice”: “140000”,
    “sortBy”:“xl|jg|pl|sj”,
    “sortWay”:“asc|desc”

    }

    sortBy: 综合(zh)、销量(xl )、价格(jg)、评论(pl)、时间(sj)
    sortWay: 升序(asc)、降序(desc)

    6.12.3初始化数据
    update tb_spu set logo = ‘https://img12.360buyimg.com/n1/jfs/t17050/106/1124838205/250590/7e63050a/5abb6be2N853106d9.jpg’
    where id in (2,3,4,5,7,124);

    6.12.4封装对象

    SearchVo,用于封装搜索条件,含分页数据,继承PageRequest
    package com.czxy.changgou4.vo;

    import com.fasterxml.jackson.annotation.JsonProperty;
    import lombok.Data;

    import java.util.Map;

    /**

    • 封装搜索条件,含分页数据
    • Created by liangtong.
      */
      @Data
      public class SearchVo extends PageRequest {
      private String keyword; // 关键字搜索,预留
      private Integer catId; // 3 级类目
      private Integer brandId; // 品牌
      private Map<String,String> specList; // 规格选项列表
      private Double minPrice; //最低价格
      private Double maxPrice; //最高价格
      }

    ReturnSku ,用于封装sku的响应结果(可以理解SearchSku缩减版,为页面定制)

    package com.czxy.changgou4.vo;

    import lombok.Data;

    /**

    • Sku页面定制响应对象
    • Created by liangtong.
      */
      @Data
      public class ReturnSku {
      private Long id;
      private String goodsName; //sku名称
      private Double price; //价格
      private String midlogo; //sku logo
      private Integer commentCount; //sku的评论
      }

    6.12.5实现
    步骤一:编写service接口,使用 SkuRepository 从 es中进行查询
    编写二:编写service实现类
    步骤三:编写controller,从es查询指定分类的sku信息

    步骤一:编写service接口,使用 SkuRepository 从 es中进行查询
    package com.czxy.changgou4.service;

    import com.czxy.changgou4.vo.SearchVo;

    import java.util.Map;

    /**

    • @author 桐叔
    • @email [email protected]
      /
      public interface SkuSearchService {
      /
      *
      • 根据条件查询sku,并将结果封装到Map中
      • map.count , 总条数
      • map.list , 总记录
      • @param searchVo
      • @return 自定义封装数据
        */
        public Map search(SearchVo searchVo);
        }

    步骤二:编写service实现类

    package com.czxy.changgou4.service.impl;

    import com.czxy.changgou4.repository.SkuRepository;
    import com.czxy.changgou4.service.SkuSearchService;
    import com.czxy.changgou4.vo.ReturnSku;
    import com.czxy.changgou4.vo.SearchSku;
    import com.czxy.changgou4.vo.SearchVo;
    import org.apache.commons.lang3.StringUtils;
    import org.elasticsearch.index.query.BoolQueryBuilder;
    import org.elasticsearch.index.query.QueryBuilders;
    import org.elasticsearch.search.sort.SortBuilders;
    import org.elasticsearch.search.sort.SortOrder;
    import org.springframework.data.domain.Page;
    import org.springframework.data.domain.PageRequest;
    import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
    import org.springframework.stereotype.Service;

    import javax.annotation.Resource;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @Service
      public class SkuSearchServiceImpl implements SkuSearchService {
      @Resource
      private SkuRepository skuRepository;

      @Override
      public Map search(SearchVo searchVo) {
      //1 创建查询构建器
      BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

       //1.1 分类查询(必须)
       boolQueryBuilder.must(QueryBuilders.termQuery("catId",searchVo.getCatId()));
      
       //1.2 关键字查询
       if(StringUtils.isNotBlank(searchVo.getKeyword())){
           boolQueryBuilder.must(QueryBuilders.matchQuery("skuName",searchVo.getKeyword()));
       }
      
       //1.3 品牌查询
       if(searchVo.getBrandId()!=null){
           boolQueryBuilder.must(QueryBuilders.termQuery("brandId",searchVo.getBrandId()));
       }
      
       //1.4 规格查询
       Map<String, String> filter = searchVo.getSpecList();
       if(filter!=null){
           for (Map.Entry<String, String> entry : filter.entrySet()) {
               String key = entry.getKey();
               String value = entry.getValue();
               boolQueryBuilder.must(QueryBuilders.termQuery( "specs." + key + ".keyword", value));
           }
       }
      
       //1.5 价格区间,统一单位:前端元 * 100 --> 后端分
       if(searchVo.getMinPrice()!=null ){
           boolQueryBuilder.must(QueryBuilders.rangeQuery("price").gte(searchVo.getMinPrice() * 100));
       }
       if(searchVo.getMaxPrice()!=null){
           boolQueryBuilder.must(QueryBuilders.rangeQuery("price").lte(searchVo.getMaxPrice() * 100));
       }
      
       //2 复杂查询
       NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
       queryBuilder.withQuery( boolQueryBuilder );
      
       //2.1 排序
       // sortBy: 综合(zh)、销量(xl )、价格(jg)、评价(pj)、时间(sj)
       // sortBy: 升序(asc)、降序(desc)
       if (searchVo.getSortBy()!=null){
           if(searchVo.getSortBy().equals("xl")&&searchVo.getSortWay().equals("asc")){
               //销量升序
               queryBuilder.withSort(SortBuilders.fieldSort("sellerCount").order(SortOrder.ASC));
           }else  if(searchVo.getSortBy().equals("xl")&&searchVo.getSortWay().equals("desc")) {
               // 销量降序
               queryBuilder.withSort(SortBuilders.fieldSort("sellerCount").order(SortOrder.DESC));
           }else if(searchVo.getSortBy().equals("jg")&&searchVo.getSortWay().equals("asc")){
               // 价格升序
               queryBuilder.withSort(SortBuilders.fieldSort("price").order(SortOrder.ASC));
           }else  if(searchVo.getSortBy().equals("jg")&&searchVo.getSortWay().equals("desc")) {
               // 价格降序
               queryBuilder.withSort(SortBuilders.fieldSort("price").order(SortOrder.DESC));
           }else if(searchVo.getSortBy().equals("pl")&&searchVo.getSortWay().equals("asc")){
               // 评论升序
               queryBuilder.withSort(SortBuilders.fieldSort("commentCount").order(SortOrder.ASC));
           }else  if(searchVo.getSortBy().equals("pl")&&searchVo.getSortWay().equals("desc")) {
               // 评论降序
               queryBuilder.withSort(SortBuilders.fieldSort("commentCount").order(SortOrder.DESC));
           }else if(searchVo.getSortBy().equals("sj")&&searchVo.getSortWay().equals("asc")){
               // 上架时间
               queryBuilder.withSort(SortBuilders.fieldSort("onSaleTime").order(SortOrder.ASC));
           }else  if(searchVo.getSortBy().equals("sj")&&searchVo.getSortWay().equals("desc")) {
               // 上架时间
               queryBuilder.withSort(SortBuilders.fieldSort("onSaleTime").order(SortOrder.DESC));
           }
       }
      
       // 2.2 分页
       queryBuilder.withPageable(PageRequest.of(searchVo.getPageNum() - 1 ,searchVo.getPageSize()));
      
      
       //3 查询,获取结果
       // 3.1 查询
       Page<SearchSku> pageInfo = this.skuRepository.search(queryBuilder.build());
       // 2.2 总条数
       long total = pageInfo.getTotalElements();
      
       // 3.3 获取返回结果 ,组装返回数据
       List<SearchSku> list = pageInfo.getContent();
       // 返回的结果
       List<ReturnSku> returnList = new ArrayList<>();
       for(SearchSku sku:list){
           ReturnSku rs = new ReturnSku();
           rs.setId(sku.getId().intValue());
           rs.setGoodsName(sku.getSkuName());
           rs.setMidlogo(sku.getLogo());
           rs.setCommentCount(sku.getCommentCount());
           rs.setPrice(sku.getPrice());
      
           returnList.add(rs);
       }
      
       // 3.4 封装
       Map result = new HashMap();
       result.put("count" , total);
       result.put("list" ,returnList);
      
       return result;
      

      }
      }

    步骤三:编写controller,从es查询指定分类的sku信息

    package com.czxy.changgou4.controller;

    import com.czxy.changgou4.service.SkuSearchService;
    import com.czxy.changgou4.vo.BaseResult;
    import com.czxy.changgou4.vo.SearchVo;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;

    import javax.annotation.Resource;
    import java.util.Map;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @RestController
      @RequestMapping(“/sku”)
      public class SkuSearchController {
      @Resource
      private SkuSearchService skuSearchService;

      @PostMapping(“/search”)
      public BaseResult findSkus(@RequestBody SearchVo searchVo){

       if(searchVo.getCatId()==null){
           return BaseResult.error("分类不能为空");
       }
      
       Map search = skuSearchService.search(searchVo);
       return BaseResult.ok("查询成功",search);
      

      }
      }

    6.12.6测试
    http://localhost:10010/search-service/swagger-ui.html
    {
    “catId”: “76”,
    “pageNum”: 1,
    “pageSize”: 3,
    “skuName”: “手机”,
    “brandId”: 8557,
    “specList”: {“机身颜色”:“白色”,“内存”:“4GB”,“机身存储”:“64GB”},
    “minPrice”: 80000,
    “maxPrice”: 90000
    }

    6.13前端实现:
    6.13.1商品搜索
    步骤一:修改apiclient.js,发送ajax

    //搜索
    search : ( params ) => {
    return axios.post(‘/search-service/sku/search’, params )
    }

    步骤二:修改data区域,添加分页条件、查询结果searchResult

    data() {
    return {
    searchMap: { //搜索条件对象
    catId: ‘’, //分类id
    brandId: ‘’, // 品牌id
    pageNum: 1, //分页:第几页
    pageSize: 10 , //分页:每页显示条数

      },
      brandList : [],   //所有品牌
      specList : [],    //所有规格
      searchResult: {} ,  //搜索结果
    
    }
    

    },

    步骤三:修改searchList函数

    async searchList () {
      // ajax 查询
      let { data } = await this.$request.search( this.searchMap )
      // 处理结果
      this.searchResult = data.data
    },
    

    步骤四:页面加载成功,查询数据

    步骤五:展示数据

        <!-- 商品列表 start-->
        <div class="goodslist mt10">
          <ul>
            <li v-for="(goods,index) in searchResult.list" :key="index">
              <dl>
                <dt><a href=""><img :src="goods.midlogo" alt="" /></a></dt>
                <dd><a href="goods.html">{
          
          {goods.goodsName}}</a></dd>
                <dd><strong>¥{
          
          {goods.price | priceHandler}}</strong></dd>
                <dd><a href=""><em>已有{
          
          {goods.commentCount}}人评价</em></a></dd>
              </dl>
            </li>
          </ul>
        </div>
        <!-- 商品列表 end-->
    

    步骤六:价格优化,将分转换成元,且保留2位小数

    filters: {
    priceHandler(value) {
    if(value) {
    return (Number(value) / 100).toFixed(2)
    }
    return value
    },
    },

    步骤七:商品名称优化

    filters: {
    substr(value, len, suffix) {
    // 如果待处理字符串长度小于指定长度,则返回待处理字符串
    // 大于执行长度,截取字符串,并追加后缀
    return value.length < len ? value : value.substring(0,len) + suffix
    },
    },

    6.13.2商品搜索–规格
    步骤一:修改searchMap,完善搜索条件

    步骤二:完善 specSearch 函数
    specSearch (spec , option ) {
    //记录选中的id
    this.$set(spec,‘selectId’, option ? option.id : ‘’ )
    //设置选中参数
    this.searchMap.specList[spec.spec_name] = option.option_name
    //查询
    this.searchList()
    }

    6.13.3商品搜索–排序
    步骤一:修改data区域,添加排序条件

    步骤二:修改标签

    排序:

    步骤三:编写sortSearch函数

    sortSearch(sortBy) {
      if(this.searchMap.sortBy == sortBy) {
        //点击第二次切换排序方式
        this.searchMap.sortWay =  this.searchMap.sortWay == 'asc' ? 'desc' : 'asc'
      } else {
        //第一次默认升序
        this.searchMap.sortBy = sortBy
        this.searchMap.sortWay = 'asc'
      }
      this.searchList()
    }
    

    6.13.4商品搜索–价格区间

    价格:
    -

    <input type=“submit” value=“搜索” @click.prevent=“searchList” />

    检查:前端价格单位和后端价格单位,是否统一?
    后端价格单位:分
    前端价格单位:元

    6.13.5商品搜索–分页
    步骤一:拷贝分页组件

    步骤二:导入分页组件

    步骤三:使用分页组件

        <!--
    

    组件
                total属性:总条数 (查询结果)
                page_size:每页显示个数(查询条件)
                @page_changed :每一页需要执行函数
                  参数pageNum:表示第几页(当前页)
    –>
            <pagination :total=“searchResult.count”
                  :page_size=“searchMap.pageSize”
                  @page_changed=“pageChanged”>
            

    步骤四:编写处理函数pageChanged

    pageChanged : function( pageNum ){
      //根据第几页查询数据
      this.searchMap.pageNum = pageNum;
      //查询
      this.searchList();
    },
    

    6.13.6商品搜索–关键字搜索
    分析

    步骤一:修改 ~/pages/store/index.js ,在state区域添加keyword关键字

    步骤二:修改 ~components/HeaderSearch.vue,搜索框绑定变量keyword

    data() {
    return {
    keyword: ‘’
    }
    },

    步骤三:给搜索按钮绑定事件,将搜索框绑定的数据同步到vuex

    // 获得vuex中map方法
    import {mapMutations} from ‘vuex’

    export default {
    methods: {
    …mapMutations([‘setData’]), // 获得vuex中setData方法
    search() {
    // 调用setData方法,将数据同步到vuex中keyword变量
    this.setData({‘key’:‘keyword’,‘value’: this.keyword})
    }
    },
    }

    <input type=“submit” class=“btn” @click.prevent=“search” value=“搜索” />
    步骤四:修改 ~/pages/list/_cid ,监听vuex中keyword,如果数据发送改变进行关键字查询

    watch: {
    ‘$store.state.keyword’(newValue, oldValue) {
    this.searchMap.keyword = newValue
    this.searchList()
    },
    },

    6.13.7品牌条件优化
    需求:品牌条件太多,提供控制按钮,显示或隐藏多余品牌。

    步骤一:提供变量 brandMore,用于控制多余部门品牌的显示与隐藏。

    步骤二:提供计算属性,用于控制品牌数据多少的显示

    computed: {
    brandFilter() {
    // 显示所有 或 显示前19条
    return this.brandMore ? this.brandList : this.brandList.slice(0,19)
    },
    },

    步骤三:显示提示信息

    <span style=“cursor: pointer;” @click=“changeBrandMore”>{ {this.brandMore ? ‘收取’: ‘更多’}}

    步骤四:编写changeBrandMore() 函数,用于切换brandMore
    changeBrandMore() {
    this.brandMore=!this.brandMore
    }

    7.详情页模块
    7.1构建详情页
    步骤0:确定访问路径
    http://localhost:3000?Goods?id=1

    步骤一:创建 ~pages/Goods.vue 页面

    步骤二:复制 ~/static/goods.html 内容,导入第三方资源(css、js)
    head: {
    title: ‘列表页面’,
    link: [
    {rel:‘stylesheet’,href: ‘/style/goods.css’},
    {rel:‘stylesheet’,href: ‘/style/common.css’},
    {rel:‘stylesheet’,href: ‘/style/bottomnav.css’},
    {rel:‘stylesheet’,href: ‘/style/jqzoom.css’},
    ],
    script: [
    { type: ‘text/javascript’, src: ‘/js/header.js’ },
    { type: ‘text/javascript’, src: ‘/js/goods.js’ },
    { type: ‘text/javascript’, src: ‘/js/jqzoom-core.js’ },
    ]
    },

    步骤三:导入公共资源

    步骤四:添加原页面js特效

    7.2详情
    7.2.1分析

    7.2.2接口
    GET http://localhost:10010/web-service/sku/goods/2600242
    返回值
    {
       id:“商品ID,skuid”,
       goods_name:“商品名称”,
       price:“价格”,
       on_sale_date:“上架时间”,
       comment_count:“评论数量”,
       comment_level:“评论级别(1-5)”,
       cat1_info:{
           id:“分类ID”,
           cat_name:“分类名称”
      },
       cat2_info:{
           id:“分类ID”,
           cat_name:“分类名称”
      },
       cat3_info:{
           id:“分类ID”,
           cat_name:“分类名称”
      },
       logo:{
           smlogo:“小LOGO(50x50)”,
           biglogo:“大LOGO(350x350)”,
           xbiglogo:“超大LOGO(800x800)”
      },
       photos:[
          {
               smimg:“商品图片(50x50)”,
               bigimg:“商品图片(350x350)”,
               xbigimg:“商品图片(800x800)”
          },
           …
      ],
       description:“商品描述”,
       aftersale:“售后”,
       stock:“库存量”,
       spec_list:[
          {
               id:“规格ID”,
               spec_name:“规格名称”,
               options:[
                  {
                       id:“选项ID”,
                       option_name:“选项名称”
                  }
                   …
              ]
          }
           …
      ],
       spec_info:{
               id_list:“规格ID:选项ID|规格ID:选项ID|…”,
               id_txt:“规格名称:规格选项|规格名称:规格选项|…”
      },
       sku_list:[
          {
               skuid:“SKUID”,
               id_list:“规格ID:选项ID|规格ID:选项ID|…”
          },
           …
      ]
    }

    7.2.3初始化数据
    insert into tb_sku_photo(sku_id,url) values(2600242,‘http://img12.360buyimg.com/n1/s450x450_jfs/t1/100605/24/7603/222062/5dfc6d30Ec375bf0a/e29b6690731acb24.jpg’);
    insert into tb_sku_photo(sku_id,url) values(2600242,‘http://img12.360buyimg.com/n1/s450x450_jfs/t1/110371/2/1323/189888/5dfc6d30E073c3495/cb256ec2d3cf9ae2.jpg’);
    insert into tb_sku_photo(sku_id,url) values(2600242,‘http://img12.360buyimg.com/n1/s450x450_jfs/t1/95005/38/7465/139593/5dfc6d2fEd2317126/63b5253237353618.jpg’);

    7.2.4后端实现:JavaBean

    SkuPhoto : sku对应的所有图片
    OneSkuResult:用于封装sku详情

    步骤一:创建SkuPhoto,根据tb_sku_photo表编写内容
    package com.czxy.changgou4.pojo;

    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;
    import com.fasterxml.jackson.annotation.JsonProperty;
    import lombok.Data;

    /**

    • Created by liangtong.
      */
      @TableName(“tb_sku_photo”)
      @Data
      public class SkuPhoto {

      @TableId(type = IdType.AUTO)
      private Integer id;
      //外键
      @TableField(value=“sku_id”)
      @JsonProperty(“sku_id”)
      private Integer skuId;
      @TableField(exist = false)
      private Sku sku;

      @TableField(value=“url”)
      private String url;

    }

    步骤二:创建OneSkuResult,根据接口返回结果编写内容
    package com.czxy.changgou4.vo;

    import com.czxy.changgou4.pojo.Category;
    import com.czxy.changgou4.pojo.Specification;
    import com.fasterxml.jackson.annotation.JsonProperty;
    import lombok.Data;

    import java.util.Date;
    import java.util.List;
    import java.util.Map;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @Data
      public class OneSkuResult {

      private Integer skuid;
      private Integer spuid;
      @JsonProperty(“goods_name”)
      private String goodsName;
      private Double price;
      @JsonProperty(“on_sale_date”)
      private Date onSaleDate;
      @JsonProperty(“comment_count”)
      private Integer commentCount;
      @JsonProperty(“comment_level”)
      private Integer commentLevel;
      @JsonProperty(“cat1_info”)
      private Category cat1Info;
      @JsonProperty(“cat2_info”)
      private Category cat2Info;
      @JsonProperty(“cat3_info”)
      private Category cat3Info;
      private Map<String, String> logo;
      private List photos;
      private String description;
      private String aftersale;
      private Integer stock;
      @JsonProperty(“spec_list”)
      private List specList;
      // id_list:‘规格ID:选项ID|规格ID:选项ID|…’,
      // id_txt:‘规格名称:选项名称|规格名称:选项名称|…’
      @JsonProperty(“spec_info”)
      private Map<String, String> specInfo;
      @JsonProperty(“sku_list”)
      private List<Map<String, String>> skuList;

    }

    7.2.5后端实现:Mapper

    步骤一:修改skuCommentMapper,完成“评论级别”功能

    @Select(“SELECT * FROM tb_sku where spu_id = #{spuId}”)
    @ResultMap(“skuResultMap”)
    public List findSkuBySpuId(@Param(“spuId”) Integer spuId);

    步骤二:创建SkuPhotoMapper,完成“通过skuId查询对应的所有的图片”功能
    package com.czxy.changgou4.mapper;

    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.czxy.changgou4.pojo.SkuPhoto;
    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Result;
    import org.apache.ibatis.annotations.Results;
    import org.apache.ibatis.annotations.Select;

    import java.util.List;

    /**

    • Created by liangtong.
      */
      @Mapper
      public interface SkuPhotoMapper extends BaseMapper {

      /**

      • 通过skuId查询对应的所有的图片
      • @param spuId
      • @return
        */
        @Select(“select * from tb_sku_photo where sku_id = #{spuId}”)
        @Results({
        @Result(property=“id”, column=“id”),
        @Result(property=“skuId”, column=“sku_id”),
        @Result(property=“url”, column=“url”)
        })
        public List findSkuPhotoBySkuId(Integer spuId);

    }

    步骤三:修改SkuMapper,添加“查询指定spuId的所有sku”功能

    /**

    • 查询指定spuId的所有sku
    • @param spuId
    • @return
      */
      @Select(“select * from tb_sku where spu_id = #{spuId}”)
      @ResultMap(“skuResult”)
      public List findSkuBySpuId(Integer spuId);

    7.2.6后端实现:
    步骤一:修改SkuService,添加findSkuById 方法

    /**

    • 查询详情
    • @param skuid
    • @return
      */
      public OneSkuResult findSkuById(Integer skuid);

    步骤二:修改SkuServiceImpl,完成“查询详情”功能

    package com.czxy.changgou4.service.impl;

    import com.alibaba.fastjson.JSON;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.czxy.changgou4.mapper.*;
    import com.czxy.changgou4.pojo.Sku;
    import com.czxy.changgou4.pojo.SkuPhoto;
    import com.czxy.changgou4.pojo.Specification;
    import com.czxy.changgou4.pojo.Spu;
    import com.czxy.changgou4.service.SkuService;
    import com.czxy.changgou4.vo.ESData;
    import com.czxy.changgou4.vo.OneSkuResult;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import javax.annotation.Resource;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;

    /**

    • @author 桐叔

    • @email [email protected]
      */
      @Service
      @Transactional
      public class SkuServiceImpl extends ServiceImpl<SkuMapper, Sku> implements SkuService {

      @Resource
      private SkuCommentMapper skuCommentMapper;

      @Resource
      private SpuMapper spuMapper;

      @Resource
      private CategoryMapper categoryMapper;

      @Resource
      private SkuPhotoMapper skuPhotoMapper;

      @Resource
      private SpecificationMapper specificationMapper;

      @Override
      public List findESData() {
      //1 查询所有详情sku
      List skulist = baseMapper.findAllSkus();

       //2 将SKU 转换成 ESData
       List<ESData> esDataList = new ArrayList<>();
      
       for (Sku sku:skulist){
           ESData esData = new ESData();
           // id
           esData.setId(sku.getId());
           // 图片地址
           esData.setLogo(sku.getSpu().getLogo());
           // 商品名称
           esData.setSkuName(sku.getSkuName());
           // all  “华为xx {"机身颜色":"白色","内存":"3GB","机身存储":"16GB"} 荣耀 ”
           esData.setAll(sku.getSkuName()+"   " + sku.getSpecInfoIdTxt() + "   " +sku.getSpu().getBrand().getBrandName());
           // on_sale_time
           esData.setOnSaleTime(sku.getSpu().getOnSaleTime());
           // brand_id
           esData.setBrandId(sku.getSpu().getBrandId());
           // cat_id
           esData.setCatId(sku.getSpu().getCat3Id());
           //  Map<String, Object> specs;// 可搜索的规格参数,key是参数名,值是参数值
           Map<String,Object> specs = JSON.parseObject(sku.getSpecInfoIdTxt(), Map.class);
      

    // Map newSpecs = new HashMap();
    // for(String key : specs.keySet()){
    // newSpecs.put(“spec” + key , specs.get(key));
    // }

            esData.setSpecs(specs);
            // price 价格
            esData.setPrice(sku.getPrice());
            // spu_name
            esData.setSpuName(sku.getSpu().getSpuName());
            // stock 库存
            esData.setStock(sku.getStock());
            // description
            esData.setDescription(sku.getSpu().getDescription());
            // packages;//规格与包装
            esData.setPackages(sku.getSpu().getPackages());
            // aftersale;//售后保障
            esData.setAftersale(sku.getSpu().getAftersale());
            // midlogo;
            esData.setMidlogo(sku.getSpu().getLogo());
            // comment_count; 评价数
            Integer comment_count = skuCommentMapper.findNumBySpuId(sku.getSpu().getId());
            esData.setCommentCount(comment_count);
    
            //销售量
            esData.setSellerCount(10);
    
            esDataList.add(esData);
        }
    
        return esDataList;
    
    }
    
    @Override
    public OneSkuResult findSkuById(Integer skuid) {
        OneSkuResult skuResult = new OneSkuResult();
        // 1 查找sku基本信息
        Sku sku = baseMapper.selectById(skuid);
        // 2 根据sku查找spu信息
        Spu spu = spuMapper.findSpuById(sku.getSpuId());
    
        // 3 赋值
        // skuid;
        skuResult.setSkuid(sku.getId());
        // spuid;
        skuResult.setSpuid(sku.getSpuId());
        // 商品名称
        skuResult.setGoodsName(sku.getSkuName());
        // 价格
        skuResult.setPrice(sku.getPrice());
        // 上架时间
        skuResult.setOnSaleDate(spu.getOnSaleTime());
        // 评价数
        Integer comment_count = skuCommentMapper.findNumBySpuId(spu.getId());
        skuResult.setCommentCount(comment_count);
        // 评论级别
        skuResult.setCommentLevel(skuCommentMapper.findAvgStarBySkuId(sku.getId()));
        // 一级分类
        skuResult.setCat1Info(categoryMapper.selectById(spu.getCat1Id()));
        // 二级分类
        skuResult.setCat2Info(categoryMapper.selectById(spu.getCat2Id()));
        // 三级分类
        skuResult.setCat3Info(categoryMapper.selectById(spu.getCat3Id()));
        // 第一张图片
        Map<String,String> logo = new HashMap();
        logo.put("smlogo",spu.getLogo());
        logo.put("biglogo",spu.getLogo());
        logo.put("xbiglogo",spu.getLogo());
        skuResult.setLogo(logo);
        // 通过skuId查询对应的所有的图片
        List<SkuPhoto> skuPhotoList = skuPhotoMapper.findSkuPhotoBySkuId(sku.getId());
        List<Map> photos = new ArrayList<>();
        for(SkuPhoto sp:skuPhotoList){
            Map<String,String> map = new HashMap();
            map.put("smimg",sp.getUrl());
            map.put("bigimg",sp.getUrl());
            map.put("xbigimg",sp.getUrl());
            photos.add(map);
        }
        skuResult.setPhotos(photos);
    
        // description;
        skuResult.setDescription(spu.getDescription());
        // aftersale;
        skuResult.setAftersale(spu.getAftersale());
        // stock;
        skuResult.setStock(sku.getStock());
        // List<SpecResult> spec_list; 根据分类查找规格和规格选项
        List<Specification> spec_list = specificationMapper.findSpecificationByCategoryId(spu.getCat3Id());
        skuResult.setSpecList(spec_list);
        // //id_list:'规格ID:选项ID|规格ID:选项ID|...',
        //  //id_txt:'规格名称:选项名称|规格名称:选项名称|...'
        // Map<String, String> spec_info;
        Map<String,String> spec_info = new HashMap<>();
        spec_info.put("id_list",sku.getSpecInfoIdList());
        spec_info.put("id_txt",sku.getSpecInfoIdTxt());
        skuResult.setSpecInfo(spec_info);
        // List<Map<String, String>> sku_list;
        List<Sku> skuBySpuIdList = baseMapper.findSkuBySpuId(spu.getId());
        List<Map<String, String>> sku_list = new ArrayList<>();
        for(Sku s : skuBySpuIdList){
            Map<String,String> map = new HashMap<>();
            map.put("skuid",s.getId().toString());
            map.put("id_list",s.getSpecInfoIdList());
            sku_list.add(map);
        }
        skuResult.setSkuList(sku_list);
        // 返回结果
        return skuResult;
    }
    

    }

    步骤三:修改SkuController,完成“查询详情”功能

    /**

    • 查询详情

    • @param skuid

    • @return
      */
      @GetMapping(“/goods/{skuid}”)
      public BaseResult findSkuById(@PathVariable(“skuid”) Integer skuid){
      OneSkuResult sku = skuService.findSkuById(skuid);

      return BaseResult.ok(“查询成功”, sku);
      }

    7.2.7前端实现
    详情页面需要进行SSR

    步骤一:修改 “apiserver.js”,查询详情

    //商品详情
    getGoodsInfo : (skuId) => {
    return axios.get(“/web-service/sku/goods/” + skuId)
    }

    步骤二:修改 Goods.vue 页面,使用asyncData进行查询

    async asyncData( { app, query } ) {
    //查询
    let {data:goodsInfoData} = await app.$requestServer.getGoodsInfo( query.id )
    //返回
    return {
    goodsInfo : goodsInfoData.data
    }
    },

    步骤三:修改 Goods.vue 页面,显示当前位置

    步骤四:修改 Goods.vue 页面,处理放大镜图片



    •               
    •                    
    • 步骤五:修改 Goods.vue 页面,商品详情

      • { {spec.spec_name}}:
        { {option.option_name}}

      编写specOptionSelect方法
      methods: {
      specOptionSelect(spec,option) {
      // 拼接标记符,规格id:选项id
      let flag = spec.id + ‘:’ + option.id
      // 判断id_list中是否有‘标记符’,如果没有返回-1
      return this.goodsInfo.spec_info.id_list.indexOf(flag) != -1
      }
      },

      步骤六:修复bug,图片大小的原因,导致“放大镜”中等图太大,遮盖小图
      问题图示

      解决

      7.3规格操作
      点击“规格”时,切换SKU的id

      步骤一:修改 Goods.vue 页面,给每一个规格选项绑定点击事件

      <a @click.prevent=“selectSpec(sl.id , op.id)” v-for=“(op,opi) in sl.options” :key=“opi”

      步骤二:修改 Goods.vue 页面,完成 selectSpec 函数

      selectSpec(specId, optionId) {
        // 1. spec标记, 规格id:选项id,例如,1:1
        var flag = specId + ":" + optionId;
        // 2. 将当前记录规则替换了  1:2|2:6  --> 1:1|2:6
        var re = new RegExp( specId + ":\\d+");
        var newFlag = this.goodsInfo.spec_info.id_list.replace(re ,flag);
      
        // 3. 从sku列表中,获得skuId,跳转页面
        for(var i = 0 ; i< this.goodsInfo.sku_list.length ; i ++ ){
          var id_list = this.goodsInfo.sku_list[i].id_list;
          if(id_list == newFlag) {
            location.href = "goods?id=" + this.goodsInfo.sku_list[i].skuid;
            break;
          }
        }
      }
      

      7.4评论
      7.4.1接口
      GET http://localhost:10010/web-service/comments/spu/2?current=1&size=2
      {
      “code”: 20000,
      “message”: “查询成功”,
      “data”: {
      “impressions”: [
      {
      “id”: 1,
      “title”: “口感不错”,
      “count”: 15326,
      “spu_id”: 2,
      “sku_id”: 2600242
      }
      ],
      “ratio”: {
      “common”: “33.33”,
      “bad”: “33.33”,
      “goods”: “33.33”
      },
      “comment_count”: 3,
      “comments”: [
      {
      “id”: 3,
      “userId”: 1,
      “user”: {
      “face”: “images/user3.jpg”,
      },
      “spuId”: 2,
      “skuId”: 2600248,
      “ratio”: “2”,
      “content”: “差”,
      }
      ]
      },
      “other”: {}
      }

      7.4.2分析

      7.4.3后端实现:JavaBean

      步骤一:创建 Impression ,用于封装印象表中的数据

      package com.czxy.changgou4.pojo;

      import com.baomidou.mybatisplus.annotation.IdType;
      import com.baomidou.mybatisplus.annotation.TableField;
      import com.baomidou.mybatisplus.annotation.TableId;
      import com.baomidou.mybatisplus.annotation.TableName;
      import com.fasterxml.jackson.annotation.JsonProperty;
      import lombok.Data;

      /** 印象

      • @author 桐叔

      • @email [email protected]
        */
        @TableName(“tb_impression”)
        @Data
        public class Impression {

        @TableId(type = IdType.AUTO)
        private Integer id;

        private String title;
        private Integer count;

        @TableField(“spu_id”)
        @JsonProperty(“spu_id”)
        private Integer spuId;

        @TableField(“sku_id”)
        @JsonProperty(“sku_id”)
        private Integer skuId;

      }

      步骤二:创建 CommentResult,用于封装评论相关的信息
      package com.czxy.changgou4.pojo;

      import lombok.Data;

      import java.util.List;
      import java.util.Map;

      /**

      • @author 桐叔
      • @email [email protected]
        */
        @Data
        public class CommentResult {
        private List impressions; //印象
        private Map<String,Object> ratio; //好评度
        private Integer comment_count; //评论数
        private List comments; //评论

      }

      7.4.4后端实现:
      步骤一:创建 ImpressionMapper,完成 “查询指定SpuId的所有印象”

      package com.czxy.changgou4.mapper;

      import com.baomidou.mybatisplus.core.mapper.BaseMapper;
      import com.czxy.changgou4.pojo.Impression;
      import org.apache.ibatis.annotations.Mapper;
      import org.apache.ibatis.annotations.Param;
      import org.apache.ibatis.annotations.Select;

      import java.util.List;

      /**

      • @author 桐叔

      • @email [email protected]
        */
        @Mapper
        public interface ImpressionMapper extends BaseMapper {

        /**

        • 查询指定SpuId的所有印象
        • @param spuid
        • @return
          */
          @Select(“select * from tb_impression where spu_id = #{spuid}”)
          public List findImpressionsBySpuid(@Param(“spuid”) Integer spuid);

      }

      步骤二:修改 SkuCommentMapper,完成“查询指定好评度的评论数”

      /**

      • 查询指定好评度的评论数
      • @param spuid
      • @param ratio 0好评、1中评、2差评
      • @return
        /
        @Select("select count(
        ) from tb_sku_comment where spu_id = #{spuid} and ratio = #{ratio}")
        public Integer findCommentCountByRatio(@Param(“spuid”)Integer spuid,@Param(“ratio”)Integer ratio);

      步骤三:修改 SkuCommentMapper,完成“查询SpuId的评论数”

      /**

      • 查询SpuId的评论数
      • @param spuid
      • @return
        /
        @Select("select count(
        ) from tb_sku_comment where spu_id = #{spuid}")
        public Integer findTotalCommentBySpuid(@Param(“spuid”) Integer spuid);

      步骤四:修改 skuCommentMapper,完成“查询指定spu的所有评论”

      /**

      • 查询指定spu的所有评论
      • @param spuid
      • @return
        */
        @Select(“select * from tb_sku_comment where spu_id = #{spuid} limit #{startIndex},#{size}”)
        @Results({
        @Result(property = “createdAt” , column = “created_at”),
        @Result(property = “updatedAt” , column = “updated_at”),
        @Result(property = “userId” , column = “user_id”),
        @Result(property = “skuId” , column = “sku_id”),
        @Result(property = “specList” , column = “spec_list”),
        @Result(property = “user” , one = @One(select = “com.czxy.changgou4.mapper.UserMapper.selectById”), column = “user_id”),
        })
        public List findCommentsBySpuid(@Param(“spuid”) Integer spuid, @Param(“startIndex”) Integer startIndex, @Param(“size”) Integer size);

      步骤五:创建 SkuCommentService接口,定义“查询指定spu的所有评论”方法

      package com.czxy.changgou4.service;

      import com.baomidou.mybatisplus.extension.service.IService;
      import com.czxy.changgou4.pojo.SkuComment;
      import com.czxy.changgou4.vo.CommentResult;
      import com.czxy.changgou4.vo.PageRequest;

      /**

      • @author 桐叔
      • @email [email protected]
        /
        public interface SkuCommentService extends IService {
        /
        *
        *
        • @param spuid
        • @param pageRequest
        • @return
          */
          public CommentResult findComments(Integer spuid, PageRequest pageRequest);
          }

      步骤六:创建 SkuCommentServiceImpl实现类

      package com.czxy.changgou4.service.impl;

      import com.baomidou.mybatisplus.extension.service.IService;
      import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
      import com.czxy.changgou4.mapper.ImpressionMapper;
      import com.czxy.changgou4.mapper.SkuCommentMapper;
      import com.czxy.changgou4.mapper.SkuMapper;
      import com.czxy.changgou4.pojo.Impression;
      import com.czxy.changgou4.pojo.SkuComment;
      import com.czxy.changgou4.service.SkuCommentService;
      import com.czxy.changgou4.vo.CommentResult;
      import com.czxy.changgou4.vo.PageRequest;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Service;
      import org.springframework.transaction.annotation.Transactional;

      import javax.annotation.Resource;
      import java.util.HashMap;
      import java.util.List;
      import java.util.Map;

      /**

      • @author 桐叔

      • @email [email protected]
        */
        @Service
        @Transactional
        public class SkuCommentServiceImpl extends ServiceImpl<SkuCommentMapper, SkuComment> implements SkuCommentService {

        @Resource
        private ImpressionMapper impressionMapper;

        public CommentResult findComments(Integer spuid, PageRequest pageRequest){

         CommentResult commentResult = new CommentResult();
        
         //查询所有印象
         List<Impression> impressionList = impressionMapper.findImpressionsBySpuid(spuid);
         commentResult.setImpressions(impressionList);
        
         //好评度
         Integer goodCount = baseMapper.findCommentCountByRatio(spuid,0);// 好评
         Integer commonCount = baseMapper.findCommentCountByRatio(spuid,1);// 中评
         Integer badCount = baseMapper.findCommentCountByRatio(spuid,2);// 差评
         Integer totalCount = baseMapper.findTotalCommentBySpuid(spuid);//
        
         Map<String,Object> ratio = new HashMap<>();
         ratio.put("goods", String.format("%.2f" , goodCount * 100.0 / totalCount ));
         ratio.put("common",String.format("%.2f" , commonCount * 100.0 / totalCount ));
         ratio.put("bad",String.format("%.2f" , badCount * 100.0 / totalCount ));
         commentResult.setRatio( ratio );
        
         //总评论数
         Integer comment_count = baseMapper.findNumBySpuId(spuid);
         commentResult.setComment_count(comment_count);
        
        
         //查询所有
         int startIndex = (pageRequest.getPageNum() - 1) * pageRequest.getPageSize();
         List<SkuComment> comments = baseMapper.findCommentsBySpuid(spuid, startIndex ,pageRequest.getPageSize());
         commentResult.setComments(comments);
        
        
         return commentResult;
        

        }

      }

      步骤七:创建 SkuCommentController,完成“查询指定spu的所有评论”
      package com.czxy.changgou4.controller;

      import com.czxy.changgou4.service.SkuCommentService;
      import com.czxy.changgou4.vo.BaseResult;
      import com.czxy.changgou4.vo.CommentResult;
      import com.czxy.changgou4.vo.PageRequest;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.PathVariable;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;

      import javax.annotation.Resource;

      /**

      • @author 桐叔
      • @email [email protected]
        */
        @RestController
        @RequestMapping(“/comments”)

      public class SkuCommentController {

      @Resource
      private SkuCommentService skuCommentService;
      
      
      @GetMapping("/spu/{spuid}")
      public BaseResult findCommentsByPage(@PathVariable("spuid") Integer spuid, PageRequest pageRequest){
          CommentResult comments = skuCommentService.findComments(spuid, pageRequest);
          return BaseResult.ok("查询成功", comments);
      }
      

      }

      7.4.5前端实现
      步骤一:修改 apiclient.js,添加 getComments 函数,完成查询评论操作

      //评论
      getComments : (spuid , pageSize, pageNum ) => {
      return axios.get(/web-service/comments/spu/${spuid},{
      params: {
      pageSize: pageSize,
      pageNum: pageNum
      }
      })
      },

      步骤二:修改 Goods.vue ,编写查询评论函数,用于支持分页

      data() {
      return {
      commentVo: {
      size: 2,
      current: 1,
      },
      commentResult: {
      ratio: {}
      }
      }
      },

      async pageChanged (num) {
        this.commentVo.current = num
        // ajax 查询
        let { data } = await this.$request.getComments( this.goodsInfo.spuid, this.commentVo.current, this.commentVo.size)
        // 处理结果
        this.commentResult = data.data
      }
      

      步骤三:修改 Goods.vue ,页面加载成功后,查询评论(第一页)

      //评论
      this.pageChanged(1)

      步骤四:修改 Goods.vue ,展示“好评度”

      <div class="rate fl">
           <strong><em>{
              
              {comments.ratio.goods}}</em>%</strong> <br />
           <span>好评度</span>
      </div>
      <div class="percent fl">
          <dl>
               <dt>好评({
              
              {comments.ratio.goods}}%)</dt>
               <dd><div :style="{'width': comments.ratio.goods + 'px'}"></div></dd>
           </dl>
           <dl>
               <dt>中评({
              
              {comments.ratio.common}}%)</dt>
               <dd><div :style="{'width':comments.ratio.common + 'px'}"></div></dd>
           </dl>
           <dl>
               <dt>差评({
              
              {comments.ratio.bad}}%)</dt>
               <dd><div :style="{'width': comments.ratio.bad + 'px'}" ></div></dd>
           </dl>
      
      步骤五:修改 Goods.vue ,展示“买家印象”
      买家印象:
      { {ci.title}} ({ {ci.count}})

      步骤六:修改 Goods.vue ,展示“评论”


                      

                        

                          
                          
      { {cc.user.name}}

                        

                      

                      

                        

                           { {cc.created_at}}
                          
                        

                        

                          { {cc.content}}
                        

                        

                           回复(0)
                           有用(0)
                        

                      

                      

                    

                    

      步骤七:修改 Goods.vue ,展示“分页条”

      import Pagination from ‘…/components/Pagination’

                <pagination :total="comments.comment_count"
      

      :page_size=“commentPageSize”
                          @page_changed=“pageChanged” >

      8.购物车模块
      购物车数据2种形态:
      登录态:保存到服务器端的redis中
      没登录:保存在浏览器端 localStorage 中

      8.1搭建购物车服务:8095
      步骤一:创建changgou4-service-cart 项目

      步骤二:修改pom.xml文件,添加坐标

      com.czxy.changgou changgou4-common-auth com.czxy.changgou changgou4-pojo org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-data-redis redis.clients jedis com.alibaba.nacos nacos-client
      <!-- nacos 服务发现 -->
      <dependency>
          <groupId>com.alibaba.cloud</groupId>
          <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
      </dependency>
      
      <!-- openfeign 远程调用 -->
      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-openfeign</artifactId>
      </dependency>
      
      <!--swagger2-->
      <dependency>
          <groupId>io.springfox</groupId>
          <artifactId>springfox-swagger2</artifactId>
      </dependency>
      <dependency>
          <groupId>io.springfox</groupId>
          <artifactId>springfox-swagger-ui</artifactId>
      </dependency>
      
      <!--fastjson-->
      <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>fastjson</artifactId>
      </dependency>
      

      步骤三:创建yml文件,

      #端口号
      server:
      port: 8095
      spring:
      application:
      name: cart-service
      redis:
      host: 127.0.0.1
      cloud:
      nacos:
      discovery:
      server-addr: 127.0.0.1:8848 #nacos服务地址
      #自定义内容
      sc:
      jwt:
      secret: sc@Login(Auth}*^31)&czxy% # 登录校验的密钥
      pubKeyPath: D:/rsa/rsa.pub # 公钥地址
      priKeyPath: D:/rsa/rsa.pri # 私钥地址
      expire: 360 # 过期时间,单位分钟

      步骤四:拷贝JWT配合类 + Swagger + Redis

      步骤五:启动类

      package com.czxy.changgou4;

      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
      import org.springframework.cloud.openfeign.EnableFeignClients;

      /**

      • @author 桐叔
      • @email [email protected]
        */
        @SpringBootApplication
        @EnableDiscoveryClient
        @EnableFeignClients
        public class CGCartServiceApplication {
        public static void main(String[] args) {
        SpringApplication.run(CGCartServiceApplication.class, args);
        }
        }

      8.2添加到购物车
      8.2.1整体分析

      8.2.2接口
      POST http://localhost:10010/cart-service/carts
      {
      “skuid”: 2600242,
      “count”: 5,
      “checked”: true
      }

      8.2.3后端实现:JavaBean

      购物车列表项对象:CartItem (某一件商品的购买情况:商品、购买数量 等)
      package com.czxy.changgou4.cart;

      import com.fasterxml.jackson.annotation.JsonProperty;
      import lombok.Data;

      /**

      • @author 桐叔
      • @email [email protected]
        */
        @Data
        public class CartItem {
        private Integer skuid;
        private Integer spuid;
        @JsonProperty(“goods_name”)
        private String goodsName;
        private Double price;
        private Integer count;//购买数量
        private Boolean checked;
        private String midlogo;
        @JsonProperty(“spec_info”)
        private String specInfo;

      }

      购物车对象:Cart

      package com.czxy.changgou4.cart;

      import lombok.Data;

      import java.util.HashMap;
      import java.util.Map;

      /**

      • @author 桐叔

      • @email [email protected]
        */
        @Data
        public class Cart {

        private Map<Integer , CartItem > data = new HashMap<>();
        private Double total;

        public Double getTotal() {
        double sum = 0.0;
        for (CartItem cartItem : data.values()) {
        //只统计勾选的价格
        if(cartItem.getChecked()){
        sum += ( cartItem.getPrice() * cartItem.getCount());
        }
        }
        return sum;
        }

        public void addCart(CartItem cartItem) {
        CartItem temp = data.get(cartItem.getSkuid());
        if(temp == null) {
        data.put( cartItem.getSkuid() , cartItem);
        } else {
        temp.setCount( cartItem.getCount() + temp.getCount() );
        }
        }

        public void updateCart(Integer skuid, Integer count , Boolean checked) {
        CartItem temp = data.get(skuid);
        if(temp != null) {
        temp.setCount( count );
        temp.setChecked(checked);
        }
        }

        public void deleteCart(Integer skuid) {
        data.remove( skuid );
        }

      }

      购物车专门定制的对象
      CartCategory

      package com.czxy.changgou4.vo;

      import com.fasterxml.jackson.annotation.JsonInclude;
      import com.fasterxml.jackson.annotation.JsonProperty;
      import lombok.Data;

      import java.util.ArrayList;
      import java.util.List;

      /**

      • @author 桐叔

      • @email [email protected]
        */
        @Data
        public class CartCategory {

        private Integer id;

        @JsonProperty(“cat_name”)
        private String catName;

        @JsonProperty(“parent_id”)
        private Integer parentId;

        @JsonProperty(“is_parent”)
        private Boolean isParent;

        //当前分类具有的所有孩子
        @JsonInclude(JsonInclude.Include.NON_EMPTY)
        private List children = new ArrayList<>();

      }

      CartSpecificationOption
      package com.czxy.changgou4.vo;

      import com.fasterxml.jackson.annotation.JsonProperty;
      import lombok.Data;

      /**

      • @author 桐叔

      • @email [email protected]
        */
        @Data
        public class CartSpecificationOption {

        private Integer id;

        @JsonProperty(“spec_id”)
        private Integer specId; //外键,规格ID

        private CartSpecification specification; //外键对应对象

        @JsonProperty(“option_name”)
        private String optionName; //选项名称

      }

      CartSpecification

      package com.czxy.changgou4.vo;

      import com.fasterxml.jackson.annotation.JsonProperty;
      import lombok.Data;

      import java.util.List;

      /**

      • @author 桐叔

      • @email [email protected]
        */
        @Data
        public class CartSpecification {

        private Integer id;

        @JsonProperty(“spec_name”)
        private String specName; //规格名称

        private Integer categoryId; //分类外键
        private CartCategory category; //分类外键对应对象

        private List options; //一个规格,具有多个规格选项

      }

      CartOneSkuResult

      package com.czxy.changgou4.vo;

      import com.fasterxml.jackson.annotation.JsonProperty;
      import lombok.Data;

      import java.util.Date;
      import java.util.List;
      import java.util.Map;

      /**

      • @author 桐叔

      • @email [email protected]
        */
        @Data
        public class CartOneSkuResult {

        private Integer skuid;
        private Integer spuid;
        @JsonProperty(“goods_name”)
        private String goodsName;
        private Double price;
        @JsonProperty(“on_sale_date”)
        private Date onSaleDate;
        @JsonProperty(“comment_count”)
        private Integer commentCount;
        @JsonProperty(“comment_level”)
        private Integer commentLevel;
        @JsonProperty(“cat1_info”)
        private CartCategory cat1Info;
        @JsonProperty(“cat2_info”)
        private CartCategory cat2Info;
        @JsonProperty(“cat3_info”)
        private CartCategory cat3Info;
        private Map<String, String> logo;
        private List photos;
        private String description;
        private String aftersale;
        private Integer stock;
        @JsonProperty(“spec_list”)
        private List specList;
        // id_list:‘规格ID:选项ID|规格ID:选项ID|…’,
        // id_txt:‘规格名称:选项名称|规格名称:选项名称|…’
        @JsonProperty(“spec_info”)
        private Map<String, String> specInfo;
        @JsonProperty(“sku_list”)
        private List<Map<String, String>> skuList;

      }

      8.2.4后端实现

      步骤一:创建CartVo,用于封装请求参数

      package com.czxy.changgou4.vo;

      import lombok.Data;

      /**

      • @author 桐叔

      • @email [email protected]
        */
        @Data
        public class CartVo {

        private Integer skuid ; //“SKUID”,
        private Integer count; //“购买数量”
        private Boolean checked; //“购买数量”

      }

      步骤二:创建SkuClient,用于查询详情

      package com.czxy.changgou4.feign;

      import com.czxy.changgou4.vo.BaseResult;
      import com.czxy.changgou4.vo.OneSkuResult;
      import org.springframework.cloud.openfeign.FeignClient;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.PathVariable;

      /**

      • @author 桐叔

      • @email [email protected]
        */
        @FeignClient(value=“web-service”,path = “/sku”)
        public interface SkuClient {

        @GetMapping(“/goods/{skuid}”)
        public BaseResult findSkuById(@PathVariable(“skuid”) Integer skuid);

      }

      步骤三:创建CartService接口,用于完成添加业务逻辑

      package com.czxy.changgou4.service;

      import com.czxy.changgou4.pojo.User;
      import com.czxy.changgou4.vo.CartVo;

      /**

      • @author 桐叔

      • @email [email protected]
        */
        public interface CartService {

        /**

        • 给指定用户添加商品
        • @param user
        • @param cartVo
          */
          public void addCart(User user , CartVo cartVo);
          }

      步骤四:创建CartService实现类

      package com.czxy.changgou4.service.impl;

      import com.alibaba.fastjson.JSON;
      import com.czxy.changgou4.cart.Cart;
      import com.czxy.changgou4.cart.CartItem;
      import com.czxy.changgou4.feign.SkuClient;
      import com.czxy.changgou4.pojo.User;
      import com.czxy.changgou4.service.CartService;
      import com.czxy.changgou4.vo.BaseResult;
      import com.czxy.changgou4.vo.CartOneSkuResult;
      import com.czxy.changgou4.vo.CartVo;
      import com.czxy.changgou4.vo.OneSkuResult;
      import org.springframework.data.redis.core.StringRedisTemplate;
      import org.springframework.stereotype.Service;
      import org.springframework.transaction.annotation.Transactional;

      import javax.annotation.Resource;

      /**

      • @author 桐叔

      • @email [email protected]
        */
        @Service
        @Transactional
        public class CartServiceImpl implements CartService {

        @Resource
        private StringRedisTemplate stringRedisTemplate;

        @Resource
        private SkuClient skuClient;

        @Override
        public void addCart(User user, CartVo cartVo) {
        //1 获得购物车
        Cart cart;
        String key = “cart” + user.getId();
        String cartStr = stringRedisTemplate.opsForValue().get(key);
        // 处理是否有购物车,没有创建,有转换(jsonStr --> java对象 )
        if(cartStr != null){
        //如果有,将json字符串转换购物车对象
        cart = JSON.parseObject( cartStr , Cart.class);

         } else {
             //如果没有创建一个
             cart = new Cart();
         }
        
         //2 保存商品
         // 2.1 确定购物买商品
         BaseResult<CartOneSkuResult> entity = skuClient.findSkuById(cartVo.getSkuid());
         CartOneSkuResult oneSkuResult = entity.getData();
        
         // * 将OneSkuResult 转换成 CartItem
         CartItem cartItem = new CartItem();
         cartItem.setSkuid( oneSkuResult.getSkuid() );
         cartItem.setSpuid( oneSkuResult.getSpuid() );
         cartItem.setGoodsName( oneSkuResult.getGoodsName() );
         cartItem.setPrice( oneSkuResult.getPrice() );
         cartItem.setCount( cartVo.getCount() );        //购买数量,用户传递的
         cartItem.setChecked(true);
         cartItem.setMidlogo( oneSkuResult.getLogo().get("biglogo"));
         cartItem.setSpecInfo( JSON.toJSONString( oneSkuResult.getSpecInfo() ) );   //将对象转换json字符串
        
         // 2.2 添加到购物车
         cart.addCart( cartItem );
        
         System.out.println(JSON.toJSONString(cart) );
         //3 保存购物车
         stringRedisTemplate.opsForValue().set( key , JSON.toJSONString(cart) );
        

        }
        }

      步骤五:创建CartController

      package com.czxy.changgou4.controller;

      import com.czxy.changgou4.config.JwtProperties;
      import com.czxy.changgou4.pojo.User;
      import com.czxy.changgou4.service.CartService;
      import com.czxy.changgou4.utils.JwtUtils;
      import com.czxy.changgou4.vo.BaseResult;
      import com.czxy.changgou4.vo.CartVo;
      import org.springframework.web.bind.annotation.PostMapping;
      import org.springframework.web.bind.annotation.RequestBody;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;

      import javax.annotation.Resource;
      import javax.servlet.http.HttpServletRequest;

      /**

      • @author 桐叔

      • @email [email protected]
        */
        @RestController
        @RequestMapping(“/carts”)
        public class CartController {

        @Resource
        private CartService cartService;

        @Resource
        private HttpServletRequest request;

        @Resource
        private JwtProperties jwtProperties;

        @PostMapping
        public BaseResult addCart(@RequestBody CartVo cartVo){

         //1 获得用户信息
         // 1.1 获得token
         String token = request.getHeader("Authorization");
         // 1.2 解析token
         User loginUser = null;
         try {
             loginUser = JwtUtils.getObjectFromToken(token, jwtProperties.getPublicKey(),User.class);
         } catch (Exception e) {
             return BaseResult.error("token失效或未登录");
         }
        
         //2 添加操作
         cartService.addCart( loginUser , cartVo );
        
         //3 提示
         return BaseResult.ok("添加成功");
        

        }

      }

      步骤六:测试

      8.2.5前端实现:购买数量
      步骤一:修改Goods.vue ,为文本框添加键盘事件,用于校验输入的数据

      <input type=“text” name=“amount” v-model=“buyCount” @keyup.prevent=“updateCount($event)” value=“1” class=“amount”/>

      步骤二:修改Goods.vue ,完成updateCount函数

      updateCount : function(e){
        // e.target.value 获得用户输入的数据
        //使用正则处理数字
        if( /^\d+$/.test(e.target.value) ){
          //如果是数字,小于1,默认为1
          if( e.target.value < 1) {
            this.buyCount = 1;
          }
        } else {
          //默认为1
          this.buyCount = 1;
        }
      },
      

      步骤三:检查+和-已完成功能

      8.2.6前端实现:
      步骤一:修改 api.js ,完成“添加到购物车”方法

      //添加到购物车
      addToCart : ( params ) => {
      return axios.post(“/cart-service/carts”, params )
      },

      步骤二:修改Goods.vue,给“加入购物车”绑定点击事件 addToCartFn

      <input type=“submit” value=“” class=“add_btn” @click.prevent=“addToCartFn” />

      步骤三:修改Goods.vue,完成addToCartFn功能
      未登录:保存到sessionStorage
      登录:保存到redis
      待完善功能:用户登录时,将sessionStorage保存的商品信息合并到redis中
      async addToCartFn(){
      //获得登录标识
      let token = sessionStorage.getItem(“token”);
      if(token != null){
      //登录:发送ajax进行添加
      let newGoods = {
      skuid: this.KaTeX parse error: Expected 'EOF', got '}' at position 55: …yCount }̲; //登录状…request.addToCart(newGoods)
      if(data.code == 20000){
      //location.href = “flow1”
      this.$router.push(‘flow1’)
      } else {
      alert(data.data.errmsg);
      }
      return;
      }

        //未登录:在浏览器保存
        //1 准备添加物品数据
        var newGoods = {
          skuid: this.goodsInfo.skuid,
          goods_name:this.goodsInfo.goods_name,
          price:this.goodsInfo.price,
          count:this.buyCount,
          checked:true,
          midlogo:this.goodsInfo.logo.smlogo,
          spec_info: JSON.stringify(this.goodsInfo.spec_info)
        };
        //2 维护数据:本地已经存储信息 和 新添加信息 合并
        var cartStr = localStorage.getItem("cart");
        var cart;
        if(cartStr == null) {
          // 2.1 第一次添加,直接已数组方式添加
          cart = [newGoods];
        } else {
          //判断是否存在(将字符串转换数组、依次遍历)
          cart = JSON.parse(cartStr);
          //是否为新物品,默认为新的
          let isNew = true;
      
          cart.forEach( g => {
            //已有数据的id 和 新物品id 是否一样
            if(g.skuid == newGoods.skuid){
              //不是新物品
              isNew = false;
              // 2.3 已有,重复,先获得对应,修改数量
              g.count += parseInt(newGoods.count);
            }
          });
      
          if(isNew == true){
            // 2.2 已有,不重复,先获得数组,后追加
            cart.push(newGoods);
          }
      
        }
        //3 存放到浏览器
        var cartStr = JSON.stringify(cart);
        localStorage.setItem("cart" , cartStr );
        //4 跳转页面
        location.href = "flow1"
      }
      

      步骤四:编写flow1页面

      购物车

      8.3查看购物车
      8.3.1分析
      用户如果没有登录,购物车存放在浏览器端的localStorage处,且以数组的方式进行存储。
      用户如果登录了,购物车存放在redis中,以Cart对象字符串方式存储。
      问题:前后端数据不一致,无法使用一个也flow1.vue进行数据展示
      解决方案:将后端cart数据进行简化,Cart对象–>Map(data)–>List(values)

      结论:前端提供给页面统一数组,页面进行数据展示即可。

      8.3.2接口
      GET http://localhost:10010/cart-service/carts

      {
          “code”: 1,
          “message”: “查询成功”,
          “data”: {
              “data”: {
                  “2600242”: {
                      “skuid”: 2600242,
                      “spuid”: 2,
                      “price”: 84900.0,
                      “count”: 17,
                      “checked”: true,
                      “midlogo”: null,
                      “goods_name”: “华为 G9 青春版 白色 移动联通电信4G手机 双卡双待”,
                      “spec_info”: “{“id_list”:“1:1|2:6|6:22”,“id_txt”:”{\“机身颜色\”:\“白色\”,\“内存\”:\“3GB\”,\“机身存储\”:\“16GB\”}“}”
                  }
              },
              “total”: 1443300.0
          },
          “other”: {}
      }

      8.3.3后端实现
      步骤一:修改CartService,添加 queryCartList 方法,从redis查询的购物车信息
      步骤二:修改CartController,添加queryCartList 方法,仅返回购物车中的数据

      步骤一:修改CartService,添加 queryCartList 方法,
      /**
      *

      • @param user
      • @return
        */
        public Cart queryCartList(User user);

      步骤二:修改CartServiceImpl,从redis查询的购物车信息
      /**

      • 查询购物车

      • @param user

      • @return
        */
        public Cart queryCartList(User user) {
        String key = “cart” + user.getId();
        // 获取hash操作对象
        String cartString = this.stringRedisTemplate.opsForValue().get(key);

        // 2 获得购物车,如果没有创建一个
        return JSON.parseObject(cartString, Cart.class);
        }

      步骤三:修改CartController,添加queryCartList 方法,仅返回购物车中的数据

      /**

      • 查询购物车

      • @return
        */
        @GetMapping
        public BaseResult queryCartList() {

        //1 获得用户信息
        // 1.1 获得token
        String token = request.getHeader(“Authorization”);
        // 1.2 解析token
        User loginUser = null;
        try {
        loginUser = JwtUtils.getObjectFromToken(token, jwtProperties.getPublicKey(),User.class);
        } catch (Exception e) {
        return BaseResult.error(“token失效或未登录”);
        }

        Cart cart = this.cartService.queryCartList(loginUser);

        return BaseResult.ok(“查询成功”, cart.getData().values());

      }

      8.3.4前端实现:显示页面
      步骤一:创建 ~/pages/flow1.vue 组件,拷贝 ~/static/flow1.html内容

      步骤二:导入js和css

      head: {
      title: ‘首页’,
      link: [
      {rel:‘stylesheet’,href: ‘/style/cart.css’},
      ],
      script: [
      { type: ‘text/javascript’, src: ‘/js/cart1.js’ },
      ]
      },

      步骤三:添加公共组件

      8.3.5前端实现:显示购物车信息
      步骤一:修改api.js 查询购物车信息
      步骤二:页面加载成功后,获得购物车信息(如果登录从后端获取,如果没有登录从浏览器端获得)
      步骤三:遍历显示购物车信息,
      步骤四:通过计算属性,计算总价格

      步骤一:修改apiclient.js 查询购物车信息
      //查询购物车
      getCart : () => {
      return axios.get(“/cart-service/carts”)
      },

      步骤二:页面加载成功后,获得购物车信息(如果登录从后端获取,如果没有登录从浏览器端获得)
      data() {
      return {
      cart : [],        //购物车对象
      }
      },
      async mounted() {
      //获得token信息,表示登录
      this.token = sessionStorage.getItem(“token”)
          if(this.token != null) {
            //登陆:服务器获得数据
            let { data } = await this.$request.getCart()
            this.cart = data.data
          } else {

      //未登录:从浏览器本地获得购物车
            let cartStr = localStorage.getItem(“cart”);
            if(cartStr != null) {
              this.cart = JSON.parse(cartStr);
            }
          }
      },

      步骤三:遍历显示购物车信息

          <tbody>
            <!-- 购物车列表 start -->
            <tr v-for="(goods,k) in cart" :key="k">
              <td class="col1">
                <a href=""><img :src="goods.midlogo" alt="" /></a>
                <strong><a href="">{
              
              {goods.goods_name}}</a></strong>
              </td>
              <td class="col2">
                <span style="display: block;" v-for="(value,key,index) in JSON.parse(JSON.parse(goods.spec_info).id_txt)"
                  :key="index">{
              
              {key}}:{
              
              {value}}</span>
              </td>
              <td class="col3">¥<span>{
              
              { (goods.price/100).toFixed(2) }}</span></td>
              <td class="col4">
                <a href="javascript:;" class="reduce_num"  @click.prevent="minus(goods)"></a>
                <input type="text" name="amount" v-model="goods.count" @keyup="updateCount(goods,$event)" value="1" class="amount"/>
                <a href="javascript:;" class="add_num"  @click.prevent="plus(goods)"></a>
              </td>
              <td class="col5">¥<span>{
              
              { (goods.price / 100 * goods.count).toFixed(2) }}</span></td>
              <td class="col6"><a href="" @click.prevent="del(k)">删除</a></td>
            </tr>
            <!-- 购物车列表 end -->
          </tbody>
      

      步骤四:通过计算属性,计算总价格

      computed : {
          totalPrice : function(){      //计算总价格
            //所有小计的和
            let sum = 0 ;
            this.cart.forEach( g => {
              sum += (g.price * g.count);
            });
            return (sum/100).toFixed(2);
      }
        },

      8.4购物车操作:修改
      8.4.1分析

      8.4.2接口
      PUT http://localhost:10010/cart-service/carts

      8.4.3后端实现:更新
      步骤一:修改service接口
      /**

      • 更新操作:如果数据存在修改数据,如果数据不存在删除数据
      • @param user
      • @param cartVoList
        */
        public void updateCart(User user, List cartVoList) ;

      步骤二:修改service实现类

      /**

      • 更新操作:如果数据存在修改数据,如果数据不存在删除数据

      • @param user

      • @param cartVoList
        */
        public void updateCart(User user, List cartVoList) {
        //1 获得购物车
        String key = “cart” + user.getId();
        String cartStr = stringRedisTemplate.opsForValue().get(key);
        // 处理是否有购物车
        Cart cart = JSON.parseObject( cartStr , Cart.class);
        if(cart == null) {
        throw new RuntimeException(“购物车不存在”);
        }

        //2 更新
        //2.1 处理请求数据
        Map<Integer,CartVo> requestMap = new HashMap<>();
        for (CartVo cartVo : cartVoList) {
        requestMap.put(cartVo.getSkuid(), cartVo);
        }

        //2.2 处理更新和删除
        Set keySet = new HashSet<>(cart.getData().keySet());
        for (Integer skuid : keySet) {
        CartVo cartRequest = requestMap.get(skuId);
        if(cartRequest != null) {
        // 更新
        cart.updateCart(cartRequest.getSkuid() , cartRequest.getCount() ,cartRequest.getChecked());
        } else {
        // 删除
        cart.deleteCart(skuId);
        }
        }

        //3 保存购物车
        stringRedisTemplate.opsForValue().set( key , JSON.toJSONString(cart) );
        }

      步骤三:修改controller

      @PutMapping
      public BaseResult updateCart(@RequestBody List cartVoList) {

      //1 获得用户信息
      // 1.1 获得token
      String token = request.getHeader("Authorization");
      // 1.2 解析token
      User loginUser = null;
      try {
          loginUser = JwtUtils.getObjectFromToken(token, jwtProperties.getPublicKey(),User.class);
      } catch (Exception e) {
          return BaseResult.error("token失效或未登录");
      }
      
      try {
          this.cartService.updateCart(loginUser ,cartVoList);
      
          return BaseResult.ok("成功");
      } catch (Exception e) {
          return BaseResult.error("失败");
      }
      

      }

      8.4.4前端实现:修改
      步骤0:修改apiclient.js,添加 updateCart函数
      步骤一:修改flow1.vue 给按钮和文本框添加事件
      步骤二:编写修改对应的事件
      步骤三:编写购物车cart的监听函数,只要数据发生改变立即保存
      步骤四:删除之前绑定js

      步骤0:修改apiclient.js,添加 updateCart函数

      updateCart : ( params ) => {
      return axios.put(“/gccartservice/carts” ,params )
      },

      步骤一:修改flow1.vue 给按钮和文本框添加事件

      步骤二:编写修改对应的事件
      methods: {
      minus : function(goods){
      if( goods.count > 1) {
      goods.count --;
      }
      },
      plus : function(goods){
      //可以考虑库存
      goods.count ++;
      },
      updateCount : function(goods,e){
      console.info(e.target.value);
      if( /^\d+$/.test( e.target.value) ){
      goods.count = e.target.value;
      } else {
      goods.count = 1;
      }
      },

      步骤三:编写购物车cart的监听函数,只要数据发生改变立即保存
      watch: {
          //深度监听
          cart : {
            async handler(val, oldVal) {
      //1 更新/删除,数据
      if(this.token != null){
                let { data } = await this.$request.updateCart( val )
                if(data.code == 1){
                  //alert(data.message);
                }
              } else {
                //未登录
                localStorage.setItem(“cart” , JSON.stringify( val ))
      }
            },
            immediate: false, //true代表如果在 watch 里声明了之后,就会立即先去执行里面的handler方法
            deep: true      //深度监听,常用于对象下面属性的改变
          }

      },

      步骤四:删除之前绑定js

      8.4.5前端实现:全选
      步骤一:修改表格
      步骤二:修改样式
      步骤三:添加全选方法
      步骤四:监听购物车数据,修改全选状态

      步骤一:修改表格

                

      步骤二:修改样式

      .mycart .col0{width: 5%;}
      .mycart .col1{width: 35%;}

      步骤三:添加全选方法

      data() {
      return {
      cart : [],        //购物车对象
      allChecked : false, //全选状态
      }
      },

      methods: {
      checkAll : function(){
      //所有列表项的状态,与全选的状态,相反
      this.cart.forEach( g => {
      g.checked = !this.allChecked;
      });
      },
      },

      步骤四:监听购物车数据,修改全选状态
      watch: {
          //深度监听
          cart : {
            async handler(val, oldVal) {
      //1 更新/删除,数据
      if(this.token != null){
                let { data } = await this.$request.updateCart( val )
                if(data.code == 1){
                  //alert(data.message);
                }
              } else {
                //未登录
                localStorage.setItem(“cart” , JSON.stringify( val ))
      }
      //2 处理全选
      let checkCount = 0;
      this.cart.forEach( g => {
      if( g.checked ) {
      checkCount ++;
      }
      });
      //全选状态,选中个数 与 总个数 比较结果
      this.allChecked = (checkCount == this.cart.length);

      },
            immediate: false, //true代表如果在 watch 里声明了之后,就会立即先去执行里面的handler方法
            deep: true      //深度监听,常用于对象下面属性的改变
          }

      },

      8.4.6后端实现:删除数据
      只需要修改数据即可,watch已经完成删除操作

      步骤一:修改html绑定删除事件

      步骤二:编写删除函数

      del (index){
        if(window.confirm("您确定要删除吗?")){
          this.cart.splice(index , 1)
        }
      },
      

      8.5结算

      8.5.1跳转页面
      步骤一:给结算绑定事件

      submit : function(){
        if(this.token != null){
          //登录
          //location.href = "flow2.html";
          this.$router.push('flow2')
        } else {
          //确定登录成功后调整的页面
          localStorage.setItem("returnURL","flow2");
          //没有登录
          //location.href = "login";
          this.$router.push('login')
      
        }
      
      },
      

      步骤二:编写flow2.vue 组件

      步骤三:完善登录

          let returnURL = localStorage.getItem("returnURL");
          if(returnURL != null){
            //删除跳转页面缓存
            localStorage.removeItem("returnURL");
            //需要跳转页面
            location.href= returnURL;
          } else {
            //默认:跳转首页
            location.href = "/";
          }
      

      9.订单模块
      9.1构建订单服务:8100
      步骤一:构建项目,changgou4-service-orders

      步骤二:修改pom.xml文件,添加依赖

      com.czxy.changgou changgou4-common-db com.czxy.changgou changgou4-common-auth com.czxy.changgou changgou4-pojo
      <!--web起步依赖-->
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      
      <!-- nacos 客户端 -->
      <dependency>
          <groupId>com.alibaba.nacos</groupId>
          <artifactId>nacos-client</artifactId>
      </dependency>
      
      <!-- nacos 服务发现 -->
      <dependency>
          <groupId>com.alibaba.cloud</groupId>
          <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
      </dependency>
      <!--redis-->
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-data-redis</artifactId>
      </dependency>
      <dependency>
          <groupId>redis.clients</groupId>
          <artifactId>jedis</artifactId>
      </dependency>
      <!-- openfeign 远程调用 -->
      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-openfeign</artifactId>
      </dependency>
      <!--swagger2-->
      <dependency>
          <groupId>io.springfox</groupId>
          <artifactId>springfox-swagger2</artifactId>
      </dependency>
      <dependency>
          <groupId>io.springfox</groupId>
          <artifactId>springfox-swagger-ui</artifactId>
      </dependency>
      <!--fastjson-->
      <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>fastjson</artifactId>
      </dependency>
      
      <!--微信支付-->
      <dependency>
          <groupId>com.github.wxpay</groupId>
          <artifactId>wxpay-sdk</artifactId>
          <version>0.0.3</version>
      </dependency>
      

      步骤三:修改yml 文件

      server:
      port: 8100

      spring:
      application:
      name: order-service
      datasource:
      url: jdbc:mysql://127.0.0.1:3306/changgou_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
      username: root
      password: 1234
      driver-class-name: com.mysql.jdbc.Driver
      druid:
      initial-size: 5
      min-idle: 5
      max-active: 20
      max-wait: 1000
      test-on-borrow: true
      redis:
      database: 0
      host: 127.0.0.1
      port: 6379
      cloud:
      nacos:
      discovery:
      server-addr: 127.0.0.1:8848 #nacos服务地址

      sc:
      worker:
      workerId: 1
      datacenterId: 1
      jwt:
      secret: sc@Login(Auth}*^31)&czxy% # 登录校验的密钥
      pubKeyPath: D:/rsa/rsa.pub # 公钥地址
      priKeyPath: D:/rsa/rsa.pri # 私钥地址
      expire: 360 # 过期时间,单位分钟
      pay:
      appID: wx8397f8696b538317
      mchID: 1473426802
      key: T6m9iK73b0kn9g5v426MKfHQH7X8rKwb
      httpConnectTimeoutMs: 5000
      httpReadTimeoutMs: 10000

      步骤四:启动类
      package com.czxy.changgou4;

      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
      import org.springframework.cloud.openfeign.EnableFeignClients;

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

      9.2收货人列表
      9.2.1接口
      GET http://localhost:10010/order-service/address
      {
          “code”: 20000,
          “message”: “查询成功”,
          “data”: [
              {
                  “id”: 1,
                  “userId”: 1,
                  “shr_name”: “小明”,
                  “shr_mobile”: “13344445555”,
                  “shr_province”: “江苏省”,
                  “shr_city”: “宿迁市”,
                  “shr_area”: “沭阳县”,
                  “shr_address”: “常州路57号”,
                  “isdefault”: 0
              }
          ],
          “other”: {}
      }

      9.2.2后端实现:JavaBean
      步骤一:创建Address地址对象

      步骤二:根据表结构和接口规范,编写内容

      package com.czxy.changgou4.pojo;

      import com.baomidou.mybatisplus.annotation.IdType;
      import com.baomidou.mybatisplus.annotation.TableField;
      import com.baomidou.mybatisplus.annotation.TableId;
      import com.baomidou.mybatisplus.annotation.TableName;
      import com.fasterxml.jackson.annotation.JsonProperty;
      import lombok.Data;

      /**

      • Created by liangtong.
        */
        @TableName(“tb_address”)
        @Data
        public class Address {

        @TableId(type = IdType.AUTO)
        private Integer id;
        //用户ID
        @TableField(value = “user_id”)
        private Long userId;
        //收货人姓名
        @TableField(value = “shr_name”)
        @JsonProperty(“shr_name”)
        private String shrName;
        //收货人手机
        @TableField(value = “shr_mobile”)
        @JsonProperty(“shr_mobile”)
        private String shrMobile;
        //收货人省份
        @TableField(value = “shr_province”)
        @JsonProperty(“shr_province”)
        private String shrProvince;
        //收货人城市
        @TableField(value = “shr_city”)
        @JsonProperty(“shr_city”)
        private String shrCity;
        //收货人地区
        @TableField(value = “shr_area”)
        @JsonProperty(“shr_area”)
        private String shrArea;
        //收货人详情地址
        @TableField(value = “shr_address”)
        @JsonProperty(“shr_address”)
        private String shrAddress;
        //1:默认;0:不是
        @TableField(value = “isdefault”)
        @JsonProperty(“isdefault”)
        private Integer isdefault;

      }

      9.2.3后端实现:
      需求:查询当前登录用户收货人列表
      需要通过token获得用户信息

      步骤一:拷贝配置类

      步骤二:编写mapper,使用通用mapper即可

      package com.czxy.changgou4.mapper;

      import com.baomidou.mybatisplus.core.mapper.BaseMapper;
      import com.czxy.changgou4.pojo.Address;
      import org.apache.ibatis.annotations.Mapper;

      @Mapper
      public interface AddressMapper extends BaseMapper

      {
      }

      步骤三:编写service接口,编写findAllByUserId 方法完成功能

      package com.czxy.changgou4.service;

      import com.baomidou.mybatisplus.extension.service.IService;
      import com.czxy.changgou4.pojo.Address;

      import java.util.List;

      public interface AddressService extends IService

      {

      /**
       * 查询指定用户的所有地址
       * @param userId
       * @return
       */
      public List<Address> findAllByUserId(Long userId) ;
      

      }

      步骤四:编写service实现

      步骤五:编写controller

      package com.czxy.changgou4.controller;

      import com.czxy.changgou4.config.JwtProperties;
      import com.czxy.changgou4.pojo.Address;
      import com.czxy.changgou4.pojo.User;
      import com.czxy.changgou4.service.AddressService;
      import com.czxy.changgou4.utils.JwtUtils;
      import com.czxy.changgou4.vo.BaseResult;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;

      import javax.annotation.Resource;
      import javax.servlet.http.HttpServletRequest;
      import java.util.List;

      @RestController
      @RequestMapping(“/address”)
      public class AddressController {

      @Resource
      private AddressService addressService;
      
      @Resource
      private JwtProperties jwtProperties;
      
      @Resource
      private HttpServletRequest request;
      
      @GetMapping
      public BaseResult queryAddress(){
      
          //1 获得用户信息
          // 1.1 获得token
          String token = request.getHeader("Authorization");
          // 1.2 解析token
          User loginUser = null;
          try {
              loginUser = JwtUtils.getObjectFromToken(token, jwtProperties.getPublicKey(),User.class);
          } catch (Exception e) {
              return BaseResult.error("token失效或未登录");
          }
      
          //2 查询
          List<Address> list = this.addressService.findAllByUserId( loginUser.getId() );
      
          return BaseResult.ok("查询成功", list );
      }
      

      }

      9.2.4前端实现
      需求:查询所有的收货人地址
      为了操作方便,需要使用filter过滤默认地址

      步骤一:修改 apiclient.js ,添加查询收货人列表函数

      getAddress : () => {
          return axios.get(“/order-service/address”)
        },

      步骤二:修改 flow2.vue 组件,页面加载成功,查询当前登录用户所有的收货人地址
      同时过滤默认地址数据

      mounted() {
      // 查询收获人地址
      this.getAddressFn()
      },
      data() {
      return {
      addressList: [], //所有的地址
      defaultAddress: {}, //默认地址
      }
      },
      methods: {
      async getAddressFn() {
      let { data } = await this.$request.getAddress()
      // 所有收获人地址
      this.addressList = data.data
      //默认地址
      this.defaultAddress = this.addressList.filter(item => item.isdefault == 1)[0];
      }
      },

      步骤三:修改 flow2.vue 组件,显示默认地址

      { {defaultAddress.shr_name}} { {defaultAddress.shr_mobile}}

      { {defaultAddress.shr_province}} { {defaultAddress.shr_city}} { {defaultAddress.shr_area}} { {defaultAddress.shr_address}}

      步骤四:修改flow2.vue,显示收货人地址列表


                    


    •                 
                        { {addr.shr_name}} { {addr.shr_province}} { {addr.shr_city}} { {addr.shr_area}} { {addr.shr_address}} { {addr.shr_mobile}}
                      设为默认地址
                      编辑
                      删除
                    
    • 9.3添加联系人
      9.3.1需求
      需求:新添加的联系人为默认联系人

      9.3.2显示添加表单
      修改flow2.vue,显示添加表单

      data() {
          return {
            addressList: [],      //所有的地址
            defaultAddress: {},   //默认地址
            showNew: false,       //是否显示新地址
          }
        },

      使用变量,控制添加表单的显示与隐藏


                    


    •                 <input type=“radio” name=“address” checked=“addr.isdefault == 1” @click=“clickRadio(addr)” />
                        { {addr.shr_name}} { {addr.shr_province}} { {addr.shr_city}} { {addr.shr_area}} { {addr.shr_address}} { {addr.shr_mobile}}
                      设为默认地址
                      编辑
                      删除{ {showNew==false && addr.isdefault == 1}} 
                    

    •               
    • <input type=“radio” name=“address” class=“new_address”  @click=“showNew=true”  />使用新地址

    •             
                  

      9.3.3接口
      POST http://localhost:10010/order-service/address
      {
      “shr_name”: “张三”,
      “shr_mobile”: “13344445555”,
      “shr_province”: “江苏省”,
      “shr_city”: “宿迁市”,
      “shr_area”: “沭阳县”,
      “shr_address”: “常州路57号”
      }

      9.3.4后端实现
      步骤一:修改mapper,添加指定用户的地址默认状态

      package com.czxy.changgou4.mapper;

      import com.baomidou.mybatisplus.core.mapper.BaseMapper;
      import com.czxy.changgou4.pojo.Address;
      import org.apache.ibatis.annotations.Mapper;
      import org.apache.ibatis.annotations.Param;
      import org.apache.ibatis.annotations.Update;

      @Mapper
      public interface AddressMapper extends BaseMapper

      {

      /**
       * 修改用户默认地址
       * @param userId
       * @param defaultValue
       */
      @Update("update tb_address set isdefault = #{defaultValue} where user_id = #{userId}")
      public void updateDefault(@Param("userId") Integer userId, @Param("defaultValue") int defaultValue);
      

      }

      步骤二:修改service,添加地址,并将新地址设置成默认地址。

      /**

      • 添加新地址,并设置成默认地址
      • @param address
        */
        public void addAddress(Address address) ;

      步骤三:修改service实现

      /**

      • 添加新地址,并设置成默认地址

      • @param address
        */
        public void addAddress(Address address) {
        //修改指定用户的状态
        this.addressMapper.updateDefault(address.getUserId(),0);

        //添加新地址,默认
        address.setIsdefault(1);
        this.addressMapper.insert(address);
        }

      步骤四:修改controller
      @PostMapping
      public BaseResult addAddress(@RequestBody Address address){
      //1 获得用户信息
      // 1.1 获得token
      String token = request.getHeader(“Authorization”);
      // 1.2 解析token
      User loginUser = null;
      try {
      loginUser = JwtUtils.getObjectFromToken(token, jwtProperties.getPublicKey(),User.class);
      } catch (Exception e) {
      return BaseResult.error(“token失效或未登录”);
      }

      address.setUserId(loginUser.getId().intValue());
      this.addressService.addAddress(address);
      return BaseResult.ok("添加成功");
      

      }

      9.3.5前端实现
      步骤一:修改apiclient.js,添加函数

      addNewAddress : ( params ) => {
      return axios.post(“/order-service/address” , params )
      },

      步骤二:修改 flow2.vue ,添加成员变量
      data() {
      return {
      addressList: [], //所有的地址
      defaultAddress: {}, //默认地址
      showNew : false,    //是否显示添加
      newAddress : {      //新地址,需要与表单进行数据绑定
              shr_name:“”,
              shr_mobile:“”,
              shr_province:“”,
              shr_city:“”,
              shr_area:“”,
              shr_address:“”
            },
      }
      },

      步骤三:表单元素绑定

      步骤四:绑定提交事件

      methods: {
      async getAddressFn() {
      let { data } = await this.KaTeX parse error: Expected 'EOF', got '}' at position 176: … == 1)[0]; }̲, async add…request.addNewAddress(this.newAddress)
      if(data.code == 1){
      // 重新查询
      this.getAddressFn()
      // 表单隐藏,数据清空
      this.showNew = false;
      this.newAddress = {
      shr_name:“”,
      shr_mobile:“”,
      shr_province:“”,
      shr_city:“”,
      shr_area:“”,
      shr_address:“”
      }
      }
      },
      },

      9.4显示勾选商品

      步骤一:页面加载成功,查询已经勾选商品

      async mounted() {
      // 查询收获人地址
      this.getAddressFn()

      //2 查询需要购买的物品
      let { data : cart } = await this.$request.getCart()
      this.cart = cart.data.filter( g => {
        //return true 数据返回,return false 数据被忽略
        return g.checked;
      });
      

      },

      步骤二:展示商品基本信息


                    
                      
                          
                        { {goods.goods_name}}
                      
                      
                        { {key}}:{ {value}}
                      
                      ¥{ { (goods.price/100).toFixed(2) }} 
                      { {goods.count}}
                      ¥{ { (goods.price / 100 * goods.count).toFixed(2) }}
                    
                  
                  

      步骤三:展示商品概述信息

      步骤四:使用计算属性显示总价格

      computed: {
          totalPrice : function(){      //计算总价格
            //所有小计的和
            let sum = 0 ;
            this.cart.forEach( g => {
              sum += (g.price * g.count);
            });
            return (sum/100).toFixed(2);
          }
        
        },

      9.5添加订单
      9.5.1接口:下订单
      POST http://localhost:10010/order-service/orders
      {
      “address_id”: 1
      }

      9.5.2接口:更新库存
      PUT http://localhost:10010/web-service/sku/goods/2600242?count=1

      9.5.3下订单分析

      9.5.4后端实现:JavaBean
      OrderVo:用于封装请求数据
      OrderGoods:订单详情封装对象
      Order:订单表封装对象

      OrderVo:用于封装请求数据

      package com.czxy.changgou4.vo;

      import com.fasterxml.jackson.annotation.JsonProperty;
      import lombok.Data;

      import java.util.Map;

      /**
      *
      */
      @Data
      public class OrderVo {

      //收货人地址ID
      @JsonProperty("address_id")
      private Integer addressId;
      
      //送货方式
      @JsonProperty("post_method")
      private Integer postMethod;
      
      //支付方式
      @JsonProperty("pay_method")
      private Integer payMethod;
      
      //发票
      private Map<Object,Object> invoice;
      

      }

      OrderGoods:订单详情封装对象
      package com.czxy.changgou4.pojo;

      import com.baomidou.mybatisplus.annotation.IdType;
      import com.baomidou.mybatisplus.annotation.TableField;
      import com.baomidou.mybatisplus.annotation.TableId;
      import com.baomidou.mybatisplus.annotation.TableName;
      import lombok.AllArgsConstructor;
      import lombok.Data;
      import lombok.NoArgsConstructor;
      import org.springframework.core.annotation.Order;

      import java.io.Serializable;

      /**
      *
      */
      @TableName(“tb_order_good”)
      @Data
      @NoArgsConstructor
      @AllArgsConstructor
      public class OrderGoods implements Serializable {
      @TableId(type = IdType.AUTO)
      private Integer id;

      @TableField(value ="sn")
      private Long sn;
      @TableField(exist = false)
      private Order order;
      
      @TableField(value ="sku_id")
      private Integer skuId;
      @TableField(exist = false)
      private Sku sku;
      
      @TableField(value ="spu_id")
      private Integer spuId;
      
      //购买数量
      @TableField(value ="number")
      private Integer number;
      //规格列表
      @TableField(value ="spec_list")
      private String specList;
      //商品名称
      @TableField(value ="sku_name")
      private String skuName;
      @TableField(value ="url")
      private String logo;
      //价格
      @TableField(value ="price")
      private Double price;
      

      }

      Order:订单表封装对象

      package com.czxy.changgou4.pojo;

      import com.baomidou.mybatisplus.annotation.TableField;
      import com.baomidou.mybatisplus.annotation.TableName;
      import com.fasterxml.jackson.databind.annotation.JsonSerialize;
      import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
      import lombok.AllArgsConstructor;
      import lombok.Data;
      import lombok.NoArgsConstructor;

      import java.io.Serializable;
      import java.util.Date;

      /**

      • @author 桐叔

      • @email [email protected]
        */
        @TableName(“tb_order”)
        @Data
        @NoArgsConstructor
        @AllArgsConstructor
        public class Order implements Serializable {

        //订单序列号
        @TableField(value =“sn”)
        //转换JSON时,将Long转换String,解决精度丢失问题
        @JsonSerialize(using= ToStringSerializer.class)
        private Long sn;
        @TableField(value =“created_at”)
        private Date createdAt;
        @TableField(value =“updated_at”)
        private Date updatedAt;

        //收货人姓名
        @TableField(value =“shr_name”)
        private String shrName;
        //收货人手机
        @TableField(value =“shr_mobile”)
        private String shrMobile;
        //收货人省份
        @TableField(value =“shr_province”)
        private String shrProvince;
        //收货人城市
        @TableField(value =“shr_city”)
        private String shrCity;
        //收货人地区
        @TableField(value =“shr_area”)
        private String shrArea;
        //收货人详情地址
        @TableField(value =“shr_address”)
        private String shrAddress;

        //订单状态,0:未支付、1:已支付、等待发货、2:已发货、等待收货 3:已收货、等待评论 4:已结束 5:申请售后
        @TableField(value =“status”)
        private Integer status;

        //支付时间
        @TableField(value =“pay_time”)
        private String payTime;
        //发货时间
        @TableField(value =“post_time”)
        private String postTime;
        //用户ID
        @TableField(value =“user_id”)
        private Long userId;
        @TableField(exist = false)
        private User user;
        //订单总价
        @TableField(value =“total_price”)
        private Double totalPrice;
        }

      9.5.5后端实现:更新库存
      需求:在web服务中,更新sku的库存

      步骤一:修改service,完成更新操作

      /**

      • 更新
      • @param skuid
      • @param count
        */
        public void updateSkuNum(Integer skuid, Integer count);

      步骤二:编写service实现类

      @Override
      public void updateSkuNum(Integer skuid, Integer count) {
      // 查询
      Sku sku = baseMapper.selectById(skuid);
      // 修改数据
      sku.setStock( sku.getStock() - count);
      // 更新
      baseMapper.updateById(sku);
      }

      步骤三:编写controller

      /**

      • 更新
      • @param skuid
      • @param count
      • @return
        */
        @PutMapping(“/goods/{skuid}”)
        public BaseResult updateSkuNum(@PathVariable(“skuid”) Integer skuid , @RequestParam(“count”) Integer count){
        skuService.updateSkuNum(skuid , count);
        return BaseResult.ok(“更新成功”);
        }

      9.5.6后端实现:下订单

      步骤一:雪花算法工具类

      package com.czxy.changgou4.config;

      import lombok.Data;
      import org.springframework.boot.context.properties.ConfigurationProperties;

      @Data
      @ConfigurationProperties(prefix = “sc.worker”)
      public class IdWorkerProperties {

      private long workerId;// 当前机器id
      
      private long datacenterId;// 序列号
      

      }

      package com.czxy.changgou4.config;

      import com.czxy.changgou4.utils.IdWorker;
      import org.springframework.boot.context.properties.EnableConfigurationProperties;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;

      @Configuration
      @EnableConfigurationProperties(IdWorkerProperties.class)
      public class IdWorkerConfig {

      @Bean
      public IdWorker idWorker(IdWorkerProperties prop) {
          return new IdWorker(prop.getWorkerId(), prop.getDatacenterId());
      }
      

      }

      步骤二:编写SkuFeign,远程更新库存数量

      package com.czxy.changgou4.feign;

      import com.czxy.changgou4.vo.BaseResult;
      import org.springframework.cloud.openfeign.FeignClient;
      import org.springframework.web.bind.annotation.PathVariable;
      import org.springframework.web.bind.annotation.PutMapping;
      import org.springframework.web.bind.annotation.RequestParam;

      @FeignClient(value = “web-service”,path = “/sku”)
      public interface SkuFeign {

      @PutMapping("/goods/{skuid}")
      public BaseResult updateSkuNum(@PathVariable("skuid") Integer skuid , @RequestParam("count") Integer count);
      

      }

      步骤三:编写mapper

      package com.czxy.changgou4.mapper;

      import com.baomidou.mybatisplus.core.mapper.BaseMapper;
      import com.czxy.changgou4.pojo.OrderGoods;
      import org.apache.ibatis.annotations.Mapper;

      @Mapper
      public interface OrderGoodsMapper extends BaseMapper {
      }

      package com.czxy.changgou4.mapper;

      import com.baomidou.mybatisplus.core.mapper.BaseMapper;
      import com.czxy.changgou4.pojo.Order;
      import org.apache.ibatis.annotations.Mapper;

      @Mapper
      public interface OrderMapper extends BaseMapper {
      }

      步骤四:编写service接口

      package com.czxy.changgou4.service;

      import com.baomidou.mybatisplus.extension.service.IService;
      import com.czxy.changgou4.pojo.Order;
      import com.czxy.changgou4.pojo.User;
      import com.czxy.changgou4.vo.CartVo;
      import com.czxy.changgou4.vo.OrderVo;

      import java.util.List;

      public interface OrderService extends IService {
      /**
      *
      * @param user
      * @param orderVo
      * @return
      */
      public Long createOrder(User user , OrderVo orderVo);

      }

      步骤五:编写service实现类

      package com.czxy.changgou4.service.impl;

      import com.alibaba.fastjson.JSON;
      import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
      import com.czxy.changgou4.cart.Cart;
      import com.czxy.changgou4.cart.CartItem;
      import com.czxy.changgou4.feign.SkuFeign;
      import com.czxy.changgou4.mapper.AddressMapper;
      import com.czxy.changgou4.mapper.OrderGoodsMapper;
      import com.czxy.changgou4.mapper.OrderMapper;
      import com.czxy.changgou4.pojo.Address;
      import com.czxy.changgou4.pojo.Order;
      import com.czxy.changgou4.pojo.OrderGoods;
      import com.czxy.changgou4.pojo.User;
      import com.czxy.changgou4.service.OrderService;
      import com.czxy.changgou4.utils.IdWorker;
      import com.czxy.changgou4.vo.CartVo;
      import com.czxy.changgou4.vo.OrderVo;
      import org.springframework.data.redis.core.StringRedisTemplate;
      import org.springframework.stereotype.Service;
      import org.springframework.transaction.annotation.Transactional;

      import javax.annotation.Resource;
      import java.util.*;

      @Service
      @Transactional
      public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {
      @Resource
      private IdWorker idWorker;

      @Resource
      private AddressMapper addressMapper;
      
      @Resource
      private StringRedisTemplate stringRedisTemplate;
      
      @Resource
      private OrderMapper orderMapper;
      
      @Resource
      private OrderGoodsMapper orderGoodsMapper;
      
      @Resource
      private SkuFeign skuFeign;
      
      public Long createOrder(User user , OrderVo orderVo) {
          //1 生成订单
          Order order = new Order();
      
          //1.1 设置订单号
          // 生成orderId
          long sn = idWorker.nextId();
          order.setSn(sn);
      
          //1.2 设置用户信息
          order.setUserId(user.getId());
      
          //1.3 设置地址信息
          // 获得前台传输过来的收货地址和收货人信息,生成订单,保存到数据库
          Address address = addressMapper.selectById(orderVo.getAddressId());
          order.setShrName(address.getShrName());
          order.setShrMobile(address.getShrMobile());
          order.setShrProvince(address.getShrProvince());
          order.setShrCity(address.getShrCity());
          order.setShrArea(address.getShrArea());
          order.setShrAddress(address.getShrAddress());
          //1.4 设置状态:创建订单的时候,默认情况是未支付状态
          order.setStatus(0);
          order.setCreatedAt(new Date());
      
          //2 获得购物车:从redis获得当前用户对应的购物车
          String key = "cart" + user.getId();
          String cartJsonStr = stringRedisTemplate.opsForValue().get(key);
          Cart cart = JSON.parseObject(cartJsonStr, Cart.class);
      
          //1.5 设置总价格
          order.setTotalPrice(cart.getTotal());
          //1.6 保存订单
          orderMapper.insert(order);
      
          //3 保存购物车中已经勾选的商品信息
          // 3.1 遍历购物项
          Iterator<CartItem> it = cart.getData().values().iterator();
          while (it.hasNext()) {
              CartItem cartItem = it.next();
              if (cartItem.getChecked()) {
                  // 3.2 将购物车中商品的信息赋值给OrderGoods
                  OrderGoods orderGoods = new OrderGoods();
                  orderGoods.setSn(idWorker.nextId());
                  orderGoods.setSkuId(cartItem.getSkuid());
                  orderGoods.setSpuId(cartItem.getSpuid());
                  orderGoods.setNumber(cartItem.getCount());
                  orderGoods.setSpecList(cartItem.getSpecInfo());
                  orderGoods.setSkuName(cartItem.getGoodsName());
                  orderGoods.setLogo(cartItem.getMidlogo());
                  orderGoods.setPrice(cartItem.getPrice());
                  // 3.3 保存购物车中的商品信息
                  orderGoodsMapper.insert(orderGoods);
                  // 3.4 购物车中移除该商品
                  it.remove();
                  // 3.5  远程调用方法,将该商品的数量减少
                  skuFeign.updateSkuNum(cartItem.getSkuid(), cartItem.getCount());
              }
          }
          //3.6 更新redis购物车
          stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(cart));
      
          //4 返回sn
          return sn;
      }
      

      }

      步骤五:编写controller

      package com.czxy.changgou4.controller;

      import com.czxy.changgou4.config.JwtProperties;
      import com.czxy.changgou4.pojo.User;
      import com.czxy.changgou4.service.OrderService;
      import com.czxy.changgou4.utils.JwtUtils;
      import com.czxy.changgou4.vo.BaseResult;
      import com.czxy.changgou4.vo.OrderVo;
      import org.springframework.web.bind.annotation.PostMapping;
      import org.springframework.web.bind.annotation.RequestBody;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;

      import javax.annotation.Resource;
      import javax.servlet.http.HttpServletRequest;

      @RestController
      @RequestMapping(“/orders”)
      public class OrderController {

      @Resource
      private OrderService orderService;
      
      @Resource
      private HttpServletRequest request;
      
      @Resource
      private JwtProperties jwtProperties;
      
      @PostMapping
      public BaseResult createOrder(@RequestBody OrderVo orderVo) {
      
          //1 获得用户信息
          // 1.1 获得token
          String token = request.getHeader("Authorization");
          // 1.2 解析token
          User loginUser = null;
          try {
              loginUser = JwtUtils.getObjectFromToken(token, jwtProperties.getPublicKey(),User.class);
          } catch (Exception e) {
              return BaseResult.error("token失效或未登录");
          }
      
      
          Long sn = this.orderService.createOrder(loginUser , orderVo);
      
          //将Long转换成字符串,否则丢失数据精度
          return BaseResult.ok("订单创建成功").append("sn", sn + "");
      }
      

      }

      9.5.7前端实现
      步骤一:修改api.js,编写添加订单函数

      addOrder : ( orderVo ) => {
      return axios.post(“/order-service/orders”, orderVo )
      },

      步骤二:给“提交订单”按钮绑定点击事件

      <a href=“” @click.prevent=“addOrderFn”>提交订单

      步骤三:添加订单操作

      async addOrderFn (){
      //准备数据

猜你喜欢

转载自blog.csdn.net/qq_58432443/article/details/130677700
今日推荐