Changgou Mall 4.0

Changgou Mall 4.0
1. Entering E-commerce
1.1 E-commerce industry analysis
In recent years, the world economy is undergoing digital transformation, and it has become a global consensus to vigorously develop the digital economy. The report of the Nineteenth National Congress of the Communist Party of China clearly stated that it is necessary to build a "digital China" and a "network power". my country's digital economy has entered a new stage of development, and its market size ranks second in the world. reform. E-commerce is an important part of the digital economy and one of the most active and concentrated manifestations of the digital economy.
The scale of my country's e-commerce transactions continues to expand, and the national e-commerce transaction volume has maintained rapid growth. According to data from the National Bureau of Statistics, in 2017, it was 29.16 trillion yuan, in 2018 it was 31.64 trillion yuan, in 2019 it was 34.81 trillion yuan, and in 2020 the transaction volume will reach 38.21 trillion yuan.
In recent years, the turnover of Tmall Double Eleven

1.2 The main e-commerce model
B2B
B2B (Business to Business) means that both the supply and demand sides of e-commerce transactions are merchants (or enterprises, companies), and she (he) uses Internet technology or various business network platforms to complete The process of business transactions. E-commerce is a specific and main form of modern B2B marketing.
Case: Alibaba, HC.com

C2C
C2C means Customer (Consumer) to Customer (Consumer), which means the e-commerce behavior among consumers. For example, a consumer has a computer, conducts transactions through the network, and sells it to another consumer. This type of transaction is called C2C e-commerce.
Case: Taobao, eBay, Guazi used car

B2C
B2C is the abbreviation of Business-to-Customer, and its Chinese abbreviation is "Business to Customer". "Business-to-customer" is a model of e-commerce, which is commonly referred to as a commercial retail model that sells products and services directly to consumers. This form of e-commerce is generally dominated by online retailing, which mainly uses the Internet to carry out online sales activities. B2C means that enterprises provide consumers with a new type of shopping environment through the Internet-online stores, and consumers use the Internet to shop online, pay online and other consumption behaviors.
Case: Vipshop, Lefeng.com

C2B
C2B (Consumer to Business, that is, consumer to business), is a new business model in the Internet economy era. This model changes the relationship between the original producers (enterprises and institutions) and consumers, and is a kind of consumer contribution value (Create Value), enterprise and institution consumption value (Consume Value).

The C2B model is exactly the opposite of the well-known supply and demand model (DSM, Demand Supply Model). The real C2B should first be produced by consumers and then produced by enterprises. Usually, consumers customize products and prices according to their own needs, or actively participate in product design, production and pricing. Products and prices reflect the individual needs of consumers, and manufacturers carry out customized production.
Case: Haier Mall, Shangpin Home Delivery

O2O
O2O stands for Online To Offline (Online Offline/Online to Offline), which refers to the combination of offline business opportunities and the Internet, making the Internet a platform for offline transactions. This concept originated in the United States. The concept of O2O is very broad, it can involve both online and offline, and it can be called O2O. Mainstream business management courses have introduced and paid attention to this new business model of O2O.
Case: Meituan, are you hungry

B2B2C
B2B2C is an e-commerce type of online shopping business model, B is the abbreviation of BUSINESS, C is the abbreviation of CUSTOMER, the first B refers to the supplier of goods or services, and the second B refers to the electronic Business enterprise, C means consumer.

Case: Jingdong Mall, Tmall Mall

Note: Our "Changgou E-commerce System Development" course adopts the B2C model

2. Changgou Front-Demand Analysis and System Design
E-commerce projects are divided into: front and back
Foreground: for users to use
background: for merchants to manage
The main explanation of this project: front
2.1 demand analysis

2.2 System Design
2.2.1 Technical Architecture

2.2.2 Architecture Diagram

3. Architecture construction
3.1 Database environment

The focus of this project is on the front-end and back-end, and the database provided does not have sub-databases or tables.

3.2 Back-end environment
3.2.1 Parent project: changgou4_parent_ali
 Modify the pom.xml file to determine the versions of spring boot, spring cloud, spring cloud Alibaba, etc.

<?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 Common project (basic): changgou4-common

General tool project
Basic dependencies for spring cloud-based development
Common tool classes for web development

Step 1: Modify the pom.xml file and add dependencies

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

Step 2: Copy tool class

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>

Create application.yml document
#Port number
server:
port: 10010
spring:
application:
name: changgou4-gateway
servlet:
multipart:
max-file-size: 2MB #Upload file size
cloud:
nacos:
discovery:
server-addr: 127.0 .0.1:8848 #nacos service address
gateway:
discovery:
locator:
enabled: true #Enable the function of service registration and discovery, automatically create a router and forward the request path starting with the service name to the corresponding service
lowerCaseServiceId: true #Set the request path on the request path The service name is configured in lowercase

Copy the cross-domain configuration class 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 Uncle Tong

  • @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) Hand over the custom function to nuxt
// Usage 1: In vue, this. request . xxx ( ) // Usage 2: In nuxt's async Data, content . app . request. xxx( ) // Usage 2: In nuxt's asyncData, content.app.re q u es t . xxx ( ) // Usage method 2 : in a sy n cD a t a of n ux t , con t e n t . a pp . request.xxx()
inject('request' , request)
}

Step 3: Configure the apiclient.js plug-in, modify the nuxt.conf.js configuration file to complete the operation

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

Step 4: Modify the nuxt.conf.js configuration file and configure the general settings of axios

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

Step 5: Test whether apiclient.js is configured successfully. When accessing test, 404
async mounted() { let { data } = await this.$request.test() console.info(data) },


3.3.3 Copy static resources
Copy all static resources to the static directory

Access the static page
http://localhost:3000/index.html through a browser

3.3.4 Modify the default items of the Nuxt project
1) Modify the default layout and delete the existing styles

2) Delete all content in the pages directory

3.3.5 Configure public js and css
 Modify the default layout and add public js and css

4. User module (8081)
4.1 Build environment
4.1.1 Backend web service: changgou4-service-web
Modify the pom.xml document

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

 Create application.yml document

#Port number
server:
port: 8081

spring:
application:
name: web-service #Service name
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 connection pool configuration
initial-size: 1 #Initialize connection pool size
min-idle: 1 #Minimum number of connections
max-active: 20 #Maximum number of connections
test-on-borrow: true #Verification when getting a connection will affect Performance
redis:
database: 0
host: 127.0.0.1
port: 6379
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 #nacos service address
sentinel:
transport:
dashboard: 127.0.0.1:8080

 Create a startup class

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 corresponding to the database

  • 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){ //Query user User findUser = userService.findByUsername( user.getUsername() ); //Judging if(findUser != null){ return BaseResult. error("Username already exists"); } else { return BaseResult.ok("Username is available"); } }








}

4.2.3 Front-end
Step 1: Create Register.vue

Step 2: Add public components

Step 3: Write the registration form and import the unique style

     


  
  


Step 4: Modify api.js and write an ajax function to check the username
const request = { test : ()=> { return axios.get('/test') }, //check the username checkUsername : ( username )= > { return axios. post('/web-service/user/checkusername', { username }) } }







Step 5: Modify the Register.vue page to complete the check function
Send ajax to check whether the user name is available
If available, display the corresponding information, and use the success style to display
If not available, display the corresponding information, and use the error style prompt

     


  
  


4.3 User Registration: Mobile Number Check
4.3.1 Interface
http://localhost:10010/web-service/user/checkmobile
{ "mobile": "13344445555" }

4.3.2 Backend

步骤一:修改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){ //Query user User findUser = userService.findByMobile( user.getMobile() ); //Judging if(findUser != null) { return BaseResult.error("The phone number is already registered"); } else { return BaseResult.ok("The phone number is available"); } }








4.3.3 Front-end
Step 1: Modify api.js and add checkMobile() function

const request = { test : ()=> { return axios.get('/test') }, //check username checkUsername : ( username )=> { return axios.post('/web-service/user/checkusername ', { username }) }, //check phone number checkMobile: ( mobile )=> { return axios.post('/web-service/user/checkmobile', { mobile }) } }











Step 2: Modify Register.vue, add checkMobileFn() to check the mobile phone number

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

Step 3: Write the required 2 variables
data() { return { user : { // form package data         username : "",         mobile : "" }, userMsg : { // error prompt data usernameData : "", mobileData : "" } } },










Step 4: Process the page

  • Full version

         


      
      


    4.4 User Registration: Pre-Technology – Redis

    4.5 User Registration: Pre-Technology – Ali Big Fish

    4.6 User registration: SMS verification code
    4.6.1 Analysis

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

    4.6.3 Backend
    Create SmsController class, call Ali Dayu tool class, and send SMS.

    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 { //Send SMS //1 production verification code String code = RandomStringUtils.randomNumeric(4); System.out.println(“ Verification code: " + 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 Front-end
    Step 1: Modify apiclient.js, send SMS ajax operation

    //Send SMS
    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 User Registration
    4.7.1 Interface
    http://localhost:10010/web-service/user/register
    { "mobile": "13612345677", "password": "1234", "username": "jack3", "code" :"3919" }




    4.7.2 Backend
    The server verification needs to be performed again before saving
    Whether the user name is registered
    Whether the mobile phone number is registered
    Whether the verification code is invalid
    Whether the verification code is wrong
    The password needs to be encrypted with BCrypt

    Step 1: Modify UserService interface, add register method
    /**

    • user registration
    • @param user
    • @return
      */
      public boolean register(User user) ;

    Step 2: Improve the UserServiceImpl implementation class

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

    }

    Step 3: Modify UserController and add register method

    /**

    • user registration

    • @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(“电话号码已经存在”);
      }

      //Verification code
      String code = stringRedisTemplate.opsForValue().get("sms_register" + user.getMobile()); //
      Delete the verification code in redis
      stringRedisTemplate.delete("sms_register" + user.getMobile());
      if (code == null) { return BaseResult.error("The verification code is invalid"); } if(!code.equals(user.getCode())) { return BaseResult.error("The verification code is incorrect"); }




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

      if(register) { return BaseResult.ok("registration succeeded"); } return BaseResult.error("registration failed"); }



    4.7.3 Date processing (optional)
     Write DateMetaObjectHandler for processing "creation time" and "modification date"
    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 Uncle Tong

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

    Improve the User JavaBean, set the filling method
    @TableField(value="created_at", fill = FieldFill.INSERT)
    private Date createdAt;

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

    4.7.4 Front-end
    Step 1: Modify api.js, add registration function

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

    Step 2: Process the form, bind the verification code input data, and submit the button binding event


  •               
                  
                  <button :disabled="btnDisabled" @click.prevent="sendSmsFn" >
    Send verification code { {seconds}} seconds

    { {userMsg.smsData.message}}


                

  •             

  •               
                   I have read and agree to the "User Registration Agreement"
                

  •             

  •               
                  <input type=“submit” value=“” @click.prevent=“registerFn” class=“login_btn” />
                
  • Step 3: Improve the user data in the data area

      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 Verification code: generation and display
     Step 1: back-end production verification code, and save the user
    in Redis Store the verification code key format in redis: "login" + username

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

    • @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 returns user object
      */
      @PostMapping(“/findByUsername”)
      public User findByUsername(@RequestBody User user){ //Query user User findUser = userService.findByUsername( user.getUsername() ); return findUser; }



    4.8.7 Authentication Service: Build Project (changgou4-service-auth)
    Step 1: Build Project

    Step 2: Create a pom.xml file

    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>
    

    Step 3: Create a yml file
    server:
    port: 8085
    spring:
    application:
    name: auth-service
    cloud:
    nacos:
    discovery:
    server-addr: 127.0.0.1:8848 #nacos service address

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

    Step 5: Configuration class

    4.8.8 Authentication service: User login backend
    Step 1: Create AuthUser package object (compared with User, lack of database-related comments)
    package com.czxy.changgou4.domain;

    import lombok.Data;

    import java.util.Date;

    /**

    • @author Uncle Tong

    • @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 Uncle Tong

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

      /**

      • User login
      • @param user
      • @return
        */
        public AuthUser login(AuthUser user ) ;
        }

    Step 4: Create an AuthService implementation class and verify the password through 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 Uncle Tong

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

      /**

      • User login
      • @param user
      • @return
        */
        public AuthUser login(AuthUser user ) { //Remotely query user AuthUser findUser = userFeign.findByUsername(user); if(findUser == null) { return null; } //Check whether the password is correct boolean checkpw = BCrypt .checkpw( user.getPassword(), findUser.getPassword()); if(checkpw){ return findUser; } return null; }











    }

    Step 5: Create AuthController and add login method
    The login verification code in redis matches the verification code entered by the user

    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){ //Verification verification code – delete after use String redisCode = stringRedisTemplate.opsForValue().get( “login” + user.getUsername() ); stringRedisTemplate.delete( "login" + user.getUsername() ); if(redisCode == null) { return BaseResult.error("Invalid verification code"); } if(! redisCode.equalsIgnoreCase(user.getCode())) { return BaseResult.error("Verification code error"); } //Login AuthUser loginUser = authService.login(user); if(loginUser != null ) { return BaseResult.ok("Login successful").append("loginUser ",loginUser); } else { return BaseResult.error("Username or password do not match"); } }

















      }

    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配置信息

    Step 2: Create a JwtProperties file for loading sc.jwt configuration information

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

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

      private String secret; // key

      private String pubKeyPath;// public key

      private String priKeyPath;// private key

      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){ //Verification verification code – delete after use String redisCode = stringRedisTemplate.opsForValue().get( “login” + user.getUsername() ); stringRedisTemplate.delete( "login" + user.getUsername() ); if(redisCode == null) { return BaseResult.error("Invalid verification code"); } if(! redisCode.equalsIgnoreCase(user.getCode())) { return BaseResult.error("Verification code error"); } //Login AuthUser loginUser = authService.login(user); if(loginUser != null ) { //Generate 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 Using token
    Step 1: Save the token after successful login, and modify the Login.vue page

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

    Step 2: The request automatically carries the token, modify apiclient.js, and add the token to the request header

    //参考 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 )
    }

    Step 3: Check nuxt.conf.js, change the plug-in mode to "client"
    Otherwise throw an exception "sessionStorage is not defined"
    plugins: [
    { src: '~plugins/apiclient.js', mode: 'client' }
    ] ,

    4.9.3 Verification
    of token Token verification is completed at the gateway project
    Step 1: Modify application.yml to add jwt configuration

    #Custom content
    sc:
    jwt:
    secret: sc@Login(Auth}*^31)&czxy% # Login verification key
    pubKeyPath: D:/rsa/rsa.pub # Public key address
    priKeyPath: D:/rsa/ rsa.pri # Private key address
    expire: 360 # Expiration time, in minutes

    Step 2: Create JwtProperties for loading configuration files

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

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

      private String secret; // key

      private String pubKeyPath;// public key

      private String priKeyPath;// private key

      private int expire;//token expiration time

      private PublicKey publicKey; // 公钥

      private PrivateKey privateKey; // private key

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

    • @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;
      }
      }

    Step 4: Modify the front-end apiclient.js file to handle 401 exceptions

    // Handle response exception
    $axios.onError(error => { // token invalid, server responds 401 if(error.response.status === 401) { console.error(error.response.data) redirect('/login ') } })





    api.js complete code

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

    // Handle response exception
    $axios.onError(error => { // token invalid, server responds 401 if(error.response.status === 401) { console.error(error.response.data) redirect('/login ') } })





    //Assign
    axios = $axios

    //4) Hand over the custom function to nuxt
    // Usage 1: In vue, this. request . xxx ( ) // Usage 2: In nuxt's async Data, content . app . request. xxx( ) // Usage 2: In nuxt's asyncData, content.app.re q u es t . xxx ( ) // Usage method 2 : in a sy n cD a t a of n ux t , con t e n t . a pp . request.xxx()
    inject('request' , request)
    }

    4.9.4 Whitelist
     All resources that do not need to be intercepted are configured in the yml file and released directly in the filter
     Step 1: Modify the application.yml file

    #自定义内容
    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 {
      }

    Step 3: Create the News object
    package com.czxy.changgou4.pojo according to the database table;

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

    • @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 Uncle Tong

    • @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;
      

      }
      }

    Step 4: Modify the 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 Uncle Tong

    • @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) => {

    //Assign
    axios = $axios

    //4) Hand over the custom function to nuxt
    // Usage 1: In vue, this. request . xxx ( ) // Usage 2: In nuxt's async Data, content . app . request. xxx( ) // Usage 2: In nuxt's 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;

      //All children of the current category
      @TableField(exist = false)
      @JsonInclude(JsonInclude.Include.NON_EMPTY)
      private List children = new ArrayList<>();

    }

    5.3.3 Backend Implementation: Query

    Step 1: Create 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 Uncle Tong
    • @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 Uncle Tong

    • @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;
      

      }
      }

    Step 4: Create a 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 Uncle Tong

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

      @Resource
      private CategoryService categoryService;

      @GetMapping
      public BaseResult findAll(){ //1 query List list = categoryService.findAll(); //2 package return BaseResult.ok("query successful", list); }




    }

    5.3.4 Front-end implementation
    Step 1: Modify "apiserver.js" to query all categories

    //Query all categories
    findCategorys: () => { return axios.get(“/web-service/categorys”) }

    Step 2: Modify index.vue, use asyncData query classification
    asyncData function can only be used in the pages directory

    async asyncData( { app } ) { // Query News + Query Category let [{data:newsData}, {data: categoryData}] = await Promise.all([ app. request Server . find News ( ) , app . 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 Uncle Tong

    • @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; //Specification name

      @TableField(value = "category_id")
      private Integer categoryId; //category foreign key
      private Category category; //category foreign key corresponding object

      private List options; //One specification with multiple specification 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 Uncle Tong
    • @email [email protected]
      /
      public interface SpecificationService extends IService {
      /
      *
      • Query all specifications of the specified category
      • @param categoryId
      • @return
        */
        public List findSpecificationByCategoryId(Integer categoryId);
        }

    Step 4: Create a SpecificationService implementation class

    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 Uncle Tong
    • @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 Front-end implementation
     Step 1: modify api.js, query specifications

    //specify the category
    findSpec : (categoryId) => { return axios.get('/web-service/specifications/category/' + categoryId) },

    Step 2: Modify pages/list/_id, the page is loaded successfully, query specification
    data() { return { searchMap: { //search condition object catId: '', //category id brandId: '', // brand id } , brandList : [], //all brands specList : [], //all specifications






    }
    

    },
    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()
    

    },

    Step 3: Display data

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

    Step 4: Select the specification, and the overall content will be displayed

    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: Query the number of comments

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

    • @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 Uncle Tong

    • @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 Uncle Tong

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

      }
      }

     Startup class
    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 Uncle Tong
    • @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;

      //Specification list
      //@IndexDynamicSettings
      private Map<String, Object> specs;// Searchable specification parameters, key is parameter name, value is parameter value
      @Field(type = FieldType.Double)
      private Double price;// 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;//Specification and packaging
      @Field(type = FieldType.Text)
      private String aftersale;//After-sale guarantee
      @Field(type = FieldType.Text)
      private String midlogo;
      //Number of evaluations
      @Field(type = FieldType.Integer)
      private Integer commentCount;
      // sales
      @Field(type = FieldType.Integer)
      private Integer sellerCount;

    }

    Step 2: Remote call

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

    }

    Step 3: Test class

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

    • @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 Uncle Tong

    • @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 paging
    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 Uncle Tong

    • @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 Implementation
    Step 1: Write service interface, use SkuRepository to query from es
    Write 2: Write service implementation class
    Step 3: Write controller, query sku information of specified classification from es

    Step 1: Write service interface, use SkuRepository to query package com.czxy.changgou4.service from es
    ;

    import com.czxy.changgou4.vo.SearchVo;

    import java.util.Map;

    /**

    • @author Uncle Tong
    • @email [email protected]
      /
      public interface SkuSearchService {
      /
      *
      • Query sku according to conditions and encapsulate the results into a Map
      • map.count , the total number of entries
      • map.list , total records
      • @param searchVo
      • @return custom package data
        */
        public Map search(SearchVo searchVo);
        }

    Step 2: Write service implementation class

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

    • @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: { //search condition object catId: '', //category id brandId: '', //brand id pageNum: 1, //page: which page pageSize: 10, //page : the number of items displayed on each page





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

    },

    Step 3: Modify the searchList function

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

    Step 4: The page is loaded successfully, and the data is queried

    Step 5: Display 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-->
    

    Step 6: Price optimization, convert cents into yuan, and keep 2 decimal places

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

    Step 7: Product name optimization

    filters: { substr(value, len, suffix) { // If the length of the string to be processed is less than the specified length, return the string to be processed // If it is greater than the execution length, intercept the string and append a suffix return value.length < len ? value : value.substring(0,len) + suffix }, },





    6.13.2 Commodity Search – Specifications
    Step 1: Modify searchMap and improve search conditions

    Step 2: Improve specSearch Function
    specSearch (spec , option ) { //Record the selected id this.$set(spec,'selectId', option ? option.id : '' ) //Set the selected parameter this.searchMap.specList [spec.spec_name] = option.option_name //query this.searchList() }






    6.13.3 Product Search – Sorting
    Step 1: Modify the data area and add sorting conditions

    Step 2: Modify the label

    Sort by:

    Step 3: Write the sortSearch function

    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 Commodity Search – Price Range

    Price:
    -

    <input type="submit" value="search" @click.prevent="searchList" />

     Check: Are the front-end price unit and the back-end price unit consistent?
    Back-end price unit: cent
    Front-end price unit: yuan

    6.13.5 Product Search – Paging
    Step 1: Copy the paging component

    Step 2: Import the pagination component

    Step 3: Use the pagination component

        <!--
    

    组件
                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']), // Get the setData method search() in vuex { // Call the setData method to synchronize the data to the keyword variable in vuex this.setData({'key': 'keyword', 'value': this.keyword}) } }, }







    <input type="submit" class="btn" @click.prevent="search" value="search" /> Step
    4: Modify ~/pages/list/_cid, monitor the keyword in vuex, if the data transmission changes, proceed keyword query

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

    6.13.7 Brand condition optimization
    Requirement: There are too many brand conditions, and control buttons are provided to display or hide redundant brands.

    Step 1: Provide the variable brandMore to control the display and hiding of redundant department brands.

    Step 2: Provide calculated attributes to control the display of brand data

    computed: { brandFilter() { // Display all or display the first 19 return this.brandMore ? this.brandList : this.brandList.slice(0,19) }, },




    Step 3: Display prompt information

    <span style="cursor: pointer;" @click="changeBrandMore">{ {this.brandMore ? 'Receive': 'More'}}

    Step 4: Write the changeBrandMore() function to switch brandMore
    changeBrandMore() { this.brandMore=!this.brandMore }

    7. Details page module
    7.1 Build details page
    Step 0: Determine the access path
    http://localhost:3000?Goods?id=1

    Step 1: Create ~pages/Goods.vue page

    Step 2: Copy the content of ~/static/goods.html and import third-party resources (css, js)
    head: { title: 'list page', 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' }, ] },












    Step 3: Import public resources

    Step 4: Add the original page js special effects

    7.2 Details
    7.2.1 Analysis

    7.2.2 Interface
    GET http://localhost:10010/web-service/sku/goods/2600242
    return value
    {    id: "product ID, skuid",    goods_name: "product name",    price: "price",    on_sale_date: " Shelving time",    comment_count: "Number of comments",    comment_level: "Comment level (1-5)",    cat1_info:{        id: "Category ID",        cat_name: "Category name"   },    cat2_info:{        id: "Category ID" ,        cat_name: "category name"   },    cat3_info:{        id: "category ID",        cat_name: "category name"   },    logo:{        smlogo: "small LOGO (50x50)",        biglogo: "big 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: "Specification ID: Option ID|Specification ID: Option ID|...",            id_txt: "Specification Name: Specification Option|Specification Name: Specification Option|..."   },    sku_list: [       {            skuid: "SKUID",            id_list: "Specification ID: Option ID|Specification ID: Option 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 Comments
      7.4.1 Interface
      GET http://localhost:10010/web-service/comments/spu/2?current=1&size=2
      { "code": 20000, "message": "query successful", "data": { "impressions": [ { "id": 1, "title": "Good taste", "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 Uncle Tong
      • @email [email protected]
        */
        @SpringBootApplication
        @EnableDiscoveryClient
        @EnableFeignClients
        public class CGCartServiceApplication {
        public static void main(String[] args) {
        SpringApplication.run(CGCartServiceApplication.class, args);
        }
        }

      8.2 Add to cart
      8.2.1 Overall analysis

      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;

      }

      Shopping cart object: Cart

      package com.czxy.changgou4.cart;

      import lombok.Data;

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

      /**

      • @author Uncle Tong

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

      }

      Shopping cart specially customized object
      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 Uncle Tong

      • @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; //"Purchase Quantity"
        private Boolean checked; //"Purchase Quantity"

      }

      Step 2: Create a SkuClient for querying details

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

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

        @GetMapping(“/goods/{skuid}”)
        public BaseResult findSkuById(@PathVariable(“skuid”) Integer skuid);

      }

      Step 3: Create a CartService interface for adding business logic

      package com.czxy.changgou4.service;

      import com.czxy.changgou4.pojo.User;
      import com.czxy.changgou4.vo.CartVo;

      /**

      • @author Uncle Tong

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

        /**

        • Add items to specified users
        • @param user
        • @param cartVo
          */
          public void addCart(User user , CartVo cartVo);
          }

      Step 4: Create a CartService implementation class

      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 ,完成“添加到购物车”方法

      //add to cart
      addToCart : ( params ) => { return axios.post(“/cart-service/carts”, params ) },

      Step 2: Modify Goods.vue, and bind the click event addToCartFn to "add to shopping cart"

      <input type=“submit” value=“” class=“add_btn” @click.prevent=“addToCartFn” />

      Step 3: Modify Goods.vue to complete the addToCartFn function
      Not logged in: save to sessionStorage
      Login: save to redis
      Function to be improved: when the user logs in, merge the product information saved in sessionStorage into redis
      async addToCartFn(){ / /Get login ID let token = sessionStorage.getItem(“token”); if(token != null){ //Login: send ajax to add let newGoods = { skuid: this. KaTeX parse error: Expected 'EOF', got '}' at position 55: …yCount }̲; //login status… 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"
      }
      

      Step 4: Write the flow1 page

      shopping cart

      8.3 Viewing the shopping cart
      8.3.1 Analysis
       If the user has not logged in, the shopping cart is stored in the localStorage of the browser and stored in an array.
      If the user logs in, the shopping cart is stored in redis and stored as a Cart object string.
      Problem: The front-end and back-end data are inconsistent, and flow1.vue cannot be used for data display
      Solution: Simplify the back-end cart data, Cart object –>Map(data)–>List(values)

      Conclusion: The front end provides a unified array to the page, and the page can display the data.

      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 Front-end implementation: modification
      Step 0: Modify apiclient.js, add updateCart function
      Step 1: Modify flow1.vue to add events to buttons and text boxes
      Step 2: Write and modify corresponding events
      Step 3: Write shopping cart Cart’s monitoring function, as long as the data changes, it will be saved immediately
      Step 4: Bind js before deleting

      Step 0: Modify apiclient.js, add updateCart function

      updateCart : ( params ) => {
      return axios.put(“/gccartservice/carts” ,params )
      },

      Step 1: Modify flow1.vue to add events to buttons and text boxes

      Step 2: Write and modify the corresponding event
      methods: { minus : function(goods){ if( goods.count > 1) { goods.count --; } }, plus : function(goods){ //You can consider inventory 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 Front-end implementation: Select all
       Step 1: Modify the form
       Step 2: Modify the style
       Step 3: Add the method of selecting all
       Step 4: Monitor the shopping cart data and modify the status of the selection

      Step 1: Modify the form

                

      Step 2: Modify the style

      .mycart .col0{width: 5%;}
      .mycart .col1{width: 35%;}

      Step 3: Add the Select All method

      data() { return { cart : [], //shopping cart object allChecked : false, //full selection state } },




      methods: { checkAll : function(){ //The state of all list items is the opposite of the state of all selected 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 means that if it is declared in the watch, it will immediately execute the handler method inside
            deep: true //Deep monitoring, often used to change the properties below the object
          }

      },

      8.4.6 Backend implementation: delete data
       Only need to modify the data, watch has completed the delete operation

      Step 1: Modify the html binding delete event

      Step 2: Write the delete function

      del (index){
        if(window.confirm("您确定要删除吗?")){
          this.cart.splice(index , 1)
        }
      },
      

      8.5 Settlement

      8.5.1 Jump page
      Step 1: Bind events to settlement

      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')
      
        }
      
      },
      

      Step 2: Write the flow2.vue component

      Step 3: Complete login

          let returnURL = localStorage.getItem("returnURL");
          if(returnURL != null){
            //删除跳转页面缓存
            localStorage.removeItem("returnURL");
            //需要跳转页面
            location.href= returnURL;
          } else {
            //默认:跳转首页
            location.href = "/";
          }
      

      9. Order module
      9.1 Build order service: 8100
      Step 1: Build project, changgou4-service-orders

      Step 2: Modify the pom.xml file and add dependencies

      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>
      

      Step 3: Modify the yml file

      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 List of consignees
      9.2.1 Interface
      GET http://localhost:10010/order-service/address
      {     "code": 20000,     "message": "Query succeeded",     "data": [         {             "id": 1 ,             "userId": 1,             "shr_name": "Xiao Ming",             "shr_mobile": "13344445555",             "shr_province": "Jiangsu Province",             "shr_city": "Suqian City",             "shr_area": ​​"Shuyang County",             "shr_address": "No. 57, Changzhou Road",             "isdefault": 0         }     ],     "other": {} }
















      9.2.2 Backend implementation: JavaBean
      Step 1: Create Address object

      Step 2: Write the content according to the table structure and interface specification

      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;
        //Consignee details address
        @TableField(value = “shr_address”)
        @JsonProperty(“shr_address”)
        private String shrAddress;
        //1: default; 0: not
        @TableField( value = "isdefault")
        @JsonProperty("isdefault")
        private Integer isdefault;

      }

      9.2.3 Backend implementation:
      Requirements: Query the consignee list of the currently logged-in user
      User information needs to be obtained through token

      Step 1: Copy the configuration class

      Step 2: Write mapper, just use general 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

      {
      }

      Step 3: Write the service interface and write the findAllByUserId method to complete the function

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

      }

      Step 4: Write service implementation

      Step 5: Write the 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: order table package object

      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;

        //Consignee name
        @TableField(value = "shr_name")
        private String shrName;
        //Consignee mobile phone
        @TableField(value = "shr_mobile")
        private String shrMobile;
        //Consignee province
        @TableField(value = " shr_province")
        private String shrProvince;
        // consignee city
        @TableField(value = "shr_city")
        private String shrCity;
        // consignee area
        @TableField(value = "shr_area")
        private String shrArea;
        // consignee Detailed address
        @TableField(value = "shr_address")
        private String shrAddress;

        //Order status, 0: unpaid, 1: paid, waiting to be shipped, 2: shipped, waiting to receive 3: received, waiting for comments 4: completed 5: after-sales application @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);
      

      }

      Step 3: Write 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);

      }

      Step 5: Write service implementation class

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

      }

      Step 5: Write the 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 (){
      //准备数据

Guess you like

Origin blog.csdn.net/qq_58432443/article/details/130677700