畅购商城4.0
1.走进电商
1.1电商行业分析
近年来,世界经济正向数字化转型,大力发展数字经济成为全球共识。党的十九大报告明确提出要建设“数字中国”“网络强国”,我国数字经济发展进入新阶段,市场规模位居全球第二,数字经济与实体经济深度融合,有力促进了供给侧结构性改革。电子商务是数字经济的重要组成部分,是数字经济最活跃、最集中的表现形式之一。
我国电子商务交易规模继续扩大,全国电子商务交易额达保持高速增长。国家统计局数据显示,2017年29.16万亿元,2018年31.64万亿元,2019年34.81万亿元,2020年交易额达38.21万亿元。
最近几年天猫双十一成交额
1.2主要电商模式
B2B
B2B ( Business to Business)是指进行电子商务交易的供需双方都是商家(或企业、公司),她(他)们使用了互联网的技术或各种商务网络平台,完成商务交易的过程。电子商务是现代 B2B marketing的一种具体主要的表现形式。
案例:阿里巴巴、慧聪网
C2C
C2C即 Customer(Consumer) to Customer(Consumer),意思就是消费者个人间的电子商务行为。比如一个消费者有一台电脑,通过网络进行交易,把它出售给另外一个消费者,此种交易类型就称为C2C电子商务。
案例:淘宝、易趣、瓜子二手车
B2C
B2C是Business-to-Customer的缩写,而其中文简称为“商对客”。“商对客”是电子商务的一种模式,也就是通常说的直接面向消费者销售产品和服务商业零售模式。这种形式的电子商务一般以网络零售业为主,主要借助于互联网开展在线销售活动。B2C即企业通过互联网为消费者提供一个新型的购物环境——网上商店,消费者通过网络在网上购物、网上支付等消费行为。
案例:唯品会、乐蜂网
C2B
C2B(Consumer to Business,即消费者到企业),是互联网经济时代新的商业模式。这一模式改变了原有生产者(企业和机构)和消费者的关系,是一种消费者贡献价值(Create Value), 企业和机构消费价值(Consume Value)。
C2B模式和我们熟知的供需模式(DSM, Demand SupplyModel)恰恰相反,真正的C2B 应该先有消费者需求产生而后有企业生产,即先有消费者提出需求,后有生产企业按需求组织生产。通常情况为消费者根据自身需求定制产品和价格,或主动参与产品设计、生产和定价,产品、价格等彰显消费者的个性化需求,生产企业进行定制化生产。
案例:海尔商城、 尚品宅配
O2O
O2O即Online To Offline(在线离线/线上到线下),是指将线下的商务机会与互联网结合,让互联网成为线下交易的平台,这个概念最早来源于美国。O2O的概念非常广泛,既可涉及到线上,又可涉及到线下,可以通称为O2O。主流商业管理课程均对O2O这种新型的商业模式有所介绍及关注。
案例:美团、饿了吗
B2B2C
B2B2C是一种电子商务类型的网络购物商业模式,B是BUSINESS的简称,C是CUSTOMER的简称,第一个B指的是商品或服务的供应商,第二个B指的是从事电子商务的企业,C则是表示消费者。
案例:京东商城、天猫商城
注:我们《畅购电商系统开发》课程采用B2C模式
2.畅购前台-需求分析与系统设计
电商项目分为:前台和后台
前台:提供用户使用
后台:提供商家进行管理
本项目主要讲解时:前台
2.1需求分析
2.2系统设计
2.2.1技术架构
2.2.2架构图
3.架构搭建
3.1数据库环境
本项目的重点在前端和后端,提供的数据库,没有分库分表。
3.2后端环境
3.2.1父工程:changgou4_parent_ali
修改pom.xml文件,确定spring boot、spring cloud、spring cloud Alibaba 等版本
4.0.0
<groupId>com.czxy.changgou</groupId>
<artifactId>changgou4-parent-ali</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>changgou4_common</module>
<module>changgou4_common_auth</module>
<module>changgou4_common_db</module>
<module>changgou4_gateway</module>
<module>changgou4_pojo</module>
<module>changgou4_service_web</module>
</modules>
<!-- 1 确定spring boot的版本-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
</parent>
<!--2 确定版本-->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<spring-cloud-release.version>Hoxton.SR3</spring-cloud-release.version>
<nacos.version>1.1.0</nacos.version>
<alibaba.cloud.version>2.2.1.RELEASE</alibaba.cloud.version>
<mysql.version>5.1.32</mysql.version>
<mybatis.plus.version>3.4.0</mybatis.plus.version>
<druid.starter.version>1.1.9</druid.starter.version>
<jwt.jjwt.version>0.9.0</jwt.jjwt.version>
<jwt.joda.version>2.9.7</jwt.joda.version>
<swagger.version>2.7.0</swagger.version>
<beanutils.version>1.9.3</beanutils.version>
<aliyun.sdk.core.version>3.3.1</aliyun.sdk.core.version>
<aliyun.sdk.dysmsapi.version>1.0.0</aliyun.sdk.dysmsapi.version>
<changgou4.common.version>1.0-SNAPSHOT</changgou4.common.version>
<changgou4.common.auth.version>1.0-SNAPSHOT</changgou4.common.auth.version>
<changgou4.common.db.version>1.0-SNAPSHOT</changgou4.common.db.version>
<changgou4.pojo.version>1.0-SNAPSHOT</changgou4.pojo.version>
</properties>
<!-- 3 锁定版本-->
<dependencyManagement>
<dependencies>
<!-- sprig cloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud-release.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--nacos -->
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>${nacos.version}</version>
</dependency>
<!--nacos cloud 发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>${alibaba.cloud.version}</version>
</dependency>
<!--nacos cloud 配置 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>${alibaba.cloud.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>${alibaba.cloud.version}</version>
</dependency>
<!-- mybatis plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis.plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-annotation</artifactId>
<version>${mybatis.plus.version}</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- druid启动器 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.starter.version}</version>
</dependency>
<!--swagger2-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${swagger.version}</version>
</dependency>
<!--jwt-->
<!--JavaBean工具类,用于JavaBean数据封装-->
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>${beanutils.version}</version>
</dependency>
<!--jwt工具-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jwt.jjwt.version}</version>
</dependency>
<!--joda 时间工具类 -->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>${jwt.joda.version}</version>
</dependency>
<!--短信-->
<dependency>
<groupId>com.aliyuncs</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>${aliyun.sdk.core.version}</version>
</dependency>
<dependency>
<groupId>com.aliyuncs.dysmsapi</groupId>
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
<version>${aliyun.sdk.dysmsapi.version}</version>
</dependency>
<!--自定义项目-->
<dependency>
<groupId>com.czxy.changgou</groupId>
<artifactId>changgou4_common</artifactId>
<version>${changgou4.common.version}</version>
</dependency>
<dependency>
<groupId>com.czxy.changgou</groupId>
<artifactId>changgou4_common_auth</artifactId>
<version>${changgou4.common.auth.version}</version>
</dependency>
<dependency>
<groupId>com.czxy.changgou</groupId>
<artifactId>changgou4_common_db</artifactId>
<version>${changgou4.common.db.version}</version>
</dependency>
<dependency>
<groupId>com.czxy.changgou</groupId>
<artifactId>changgou4_pojo</artifactId>
<version>${changgou4.pojo.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
3.2.2公共项目(基础):changgou4-common
通用工具项目
基于spring cloud开发基本依赖
web开发常见的工具类
步骤一:修改pom.xml文件,添加依赖
<?xml version="1.0" encoding="UTF-8"?>
changgou4-parent-ali
com.czxy.changgou
1.0-SNAPSHOT
4.0.0
<artifactId>changgou4-common</artifactId>
<dependencies>
<!--短信-->
<dependency>
<groupId>com.aliyuncs</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
</dependency>
<dependency>
<groupId>com.aliyuncs.dysmsapi</groupId>
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
步骤二:拷贝工具类
3.2.3公共项目(认证):changgou4-common-auth
认证通用工具项目
步骤一:修改pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
changgou4-parent-ali
com.czxy.changgou
1.0-SNAPSHOT
4.0.0
<artifactId>changgou4-common-auth</artifactId>
<dependencies>
<!--通用基础-->
<dependency>
<groupId>com.czxy.changgou</groupId>
<artifactId>changgou4_common</artifactId>
</dependency>
<!--JavaBean工具类,用于JavaBean数据封装-->
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
</dependency>
<!--jwt工具-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
<!--joda 时间工具类 -->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
</dependencies>
步骤二:拷贝工具类
3.2.4公共项目(数据库):changgou4-common-db
数据库通用工具项目
步骤一:修改pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
changgou4-parent-ali
com.czxy.changgou
1.0-SNAPSHOT
4.0.0
<artifactId>changgou4-common-db</artifactId>
<dependencies>
<!--通用基础-->
<dependency>
<groupId>com.czxy.changgou</groupId>
<artifactId>changgou4-common</artifactId>
</dependency>
<!-- mybatis plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- druid启动器 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
</dependencies>
步骤二:配置类
要求所有的服务项目包名必须是“com.czxy.changgou4”,否则配置无法扫描,就需要每个项目单独拷贝。
3.2.5POJO项目:changgou4-pojo
统一管理所有的JavaBean
修改pom.xml文件
org.projectlombok
lombok
org.springframework.boot
spring-boot-starter-json
com.baomidou
mybatis-plus-annotation
3.2.6网关:changgou4-gateway
修改pom.xml文档
changgou4-parent-ali
com.czxy.changgou
1.0-SNAPSHOT
4.0.0
<artifactId>changgou4-gateway</artifactId>
<dependencies>
<!--自定义项目-->
<dependency>
<groupId>com.czxy.changgou</groupId>
<artifactId>changgou4-common-auth</artifactId>
</dependency>
<dependency>
<groupId>com.czxy.changgou</groupId>
<artifactId>changgou4-pojo</artifactId>
</dependency>
<!-- 网关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- nacos 服务发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
创建application.yml文档
#端口号
server:
port: 10010
spring:
application:
name: changgou4-gateway
servlet:
multipart:
max-file-size: 2MB #上传文件的大小
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 #nacos服务地址
gateway:
discovery:
locator:
enabled: true #开启服务注册和发现的功能,自动创建router以服务名开头的请求路径转发到对应的服务
lowerCaseServiceId: true #将请求路径上的服务名配置为小写
拷贝跨域配置类 GlobalCorsConfig
package com.czxy.changgou4.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
/**
-
@author 桐叔
-
@email liangtong@itcast.cn
*/
@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 liangtong@itcast.cn
*/
@SpringBootApplication
@EnableDiscoveryClient
public class CGGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(CGGatewayApplication.class, args );
}
}
3.3前端环境
3.3.1构建项目:changgou4-fore
步骤一:使用脚手架构建项目
npx create-nuxt-app changgou4-fore
步骤二:npm打包方式、axios第三方模块、SSR渲染模式
3.3.2整合axios
步骤一:创建~/plugins/apiclient.js文件
步骤二:编写nuxt整合模板,用于统一管理ajax请求路径
const request = {
test : ()=> {
return axios.get(‘/test’)
}
}
var axios = null
export default ({ $axios, redirect }, inject) => {
//赋值
axios = $axios
//4) 将自定义函数交于nuxt
// 使用方式1:在vue中,this.
r
e
q
u
e
s
t
.
x
x
x
(
)
/
/
使用方式
2
:在
n
u
x
t
的
a
s
y
n
c
D
a
t
a
中,
c
o
n
t
e
n
t
.
a
p
p
.
request.xxx() // 使用方式2:在nuxt的asyncData中,content.app.
request.xxx()//使用方式2:在nuxt的asyncData中,content.app.request.xxx()
inject(‘request’, request)
}
步骤三:配置apiclient.js插件,修改nuxt.conf.js配置文件完成操作
plugins: [
{ src: ‘~plugins/apiclient.js’}
],
步骤四:修改nuxt.conf.js配置文件,配置axios通用设置
axios: {
baseURL: ‘http://localhost:10010’
},
步骤五:测试 apiclient.js是否配置成功,访问test时,出现404
async mounted() {
let { data } = await this.$request.test()
console.info(data)
},
3.3.3拷贝静态资源
将所有静态资源拷贝到static目录中
通过浏览器访问静态页面
http://localhost:3000/index.html
3.3.4修改Nuxt项目默认项
1)修改默认布局,删除已有样式
2)删除pages目录下的所有内容
3.3.5配置公共js和css
修改默认布局,添加公共js和css
4.用户模块(8081)
4.1搭建环境
4.1.1后端web服务:changgou4-service-web
修改pom.xml文档
changgou4-parent-ali
com.czxy.changgou
1.0-SNAPSHOT
4.0.0
<artifactId>changgou4_service_web</artifactId>
<dependencies>
<!--自定义项目-->
<dependency>
<groupId>com.czxy.changgou</groupId>
<artifactId>changgou4_common_db</artifactId>
</dependency>
<dependency>
<groupId>com.czxy.changgou</groupId>
<artifactId>changgou4_pojo</artifactId>
</dependency>
<!--web起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- nacos 客户端 -->
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
</dependency>
<!-- nacos 服务发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<!--swagger2-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
</dependencies>
创建application.yml文档
#端口号
server:
port: 8081
spring:
application:
name: web-service #服务名
datasource:
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/changgou_db?useUnicode=true&characterEncoding=utf8
username: root
password: 1234
druid: #druid 连接池配置
initial-size: 1 #初始化连接池大小
min-idle: 1 #最小连接数
max-active: 20 #最大连接数
test-on-borrow: true #获取连接时候验证,会影响性能
redis:
database: 0
host: 127.0.0.1
port: 6379
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 #nacos服务地址
sentinel:
transport:
dashboard: 127.0.0.1:8080
创建启动类
package com.czxy.changgou4;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
- @author 桐叔
- @email liangtong@itcast.cn
*/
@SpringBootApplication
@EnableDiscoveryClient
public class Web-serviceApplication {
public static void main(String[] args) {
SpringApplication.run( Web-serviceApplication.class , args );
}
}
4.1.2后端创建JavaBean:User
在changgou4-pojo项目中添加User对象
package com.czxy.changgou4.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.beans.Transient;
import java.util.Date;
/** 与数据库对应JavaBean
-
Created by liangtong.
/
@TableName(“tb_user”)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
/
CREATE TABLEtb_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 KEYusers_mobile_unique
(mobile
),
UNIQUE KEYusers_name_unique
(name
),
UNIQUE KEYusers_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 liangtong@itcast.cn
/
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 liangtong@itcast.cn
*/
@Service
@Transactional
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Override
public User findByUsername(String username) {
return baseMapper.findByUsername(username);
}
}
步骤四:创建UserController,完成用户名检查
package com.czxy.changgou4.controller;
import com.czxy.changgou4.pojo.User;
import com.czxy.changgou4.service.UserService;
import com.czxy.changgou4.vo.BaseResult;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
/**
-
Created by liangtong.
*/
@RestController
@RequestMapping(“/user”)
public class UserController {@Resource
private UserService userService;@PostMapping(“/checkusername”)
public BaseResult checkUsername(@RequestBody User user){
//查询用户
User findUser = userService.findByUsername( user.getUsername() );
//判断
if(findUser != null){
return BaseResult.error(“用户名已经存在”);
} else {
return BaseResult.ok(“用户名可用”);
}
}
}
4.2.3前端
步骤一:创建Register.vue
步骤二:添加公共组件
步骤三:编写注册表单,并导入独有样式
用户注册
-
3-20位字符,可由中文、字母、数字和下划线组成
用户名已存在
-
6-20位字符,可使用字母、数字和符号的组合,不建议使用纯数字、纯字母、纯符号
-
请再次输入密码
-
请输入手机号码
- 发送验证码5秒
- 我已阅读并同意《用户注册协议》
手机快速注册
中国大陆手机用户,编辑短信 “XX”发送到:
1069099988
步骤四:修改api.js,编写检查用户名的ajax函数
const request = {
test : ()=> {
return axios.get(‘/test’)
},
//检查用户名
checkUsername : ( username )=> {
return axios.post(‘/web-service/user/checkusername’, { username })
}
}
步骤五:修改Register.vue页面,完成检查功能
发送ajax进行用户名是否可用检查
如果可用,显示对应信息,并使用success样式显示
如果不可用,显示对应信息,并使用error样式提示
用户注册
4.3用户注册:手机号检查
4.3.1接口
http://localhost:10010/web-service/user/checkmobile
{
“mobile”:“13344445555”
}
4.3.2后端
步骤一:修改UserService,添加 findByMobile() 方法,进行电话号码的查询
/**
- 通过手机号查询
- @param mobile
- @return
*/
User findByMobile(String mobile);
步骤二:编写UserServiceImpl,实现findByMobile() 方法
@Override
public User findByMobile(String mobile) {
// 拼凑条件
QueryWrapper queryWrapper = new QueryWrapper();
queryWrapper.eq(“mobile”, mobile);
// 查询一个
List list = baseMapper.selectList(queryWrapper);
if(list.size() == 1) {
return list.get(0);
}
return null;
}
步骤三:修改UserController,添加checkMobile() 方法
/**
- 通过手机号查询
- @param user
- @return
*/
@PostMapping(“/checkmobile”)
public BaseResult checkMobile(@RequestBody User user){
//查询用户
User findUser = userService.findByMobile( user.getMobile() );
//判断
if(findUser != null){
return BaseResult.error(“电话号码已经注册”);
} else {
return BaseResult.ok(“电话号码可用”);
}
}
4.3.3前端
步骤一:修改api.js,添加 checkMobile() 函数
const request = {
test : ()=> {
return axios.get(‘/test’)
},
//检查用户名
checkUsername : ( username )=> {
return axios.post(‘/web-service/user/checkusername’, { username })
},
//检查电话号码
checkMobile : ( mobile )=> {
return axios.post(‘/web-service/user/checkmobile’, { mobile })
}
}
步骤二:修改Register.vue,添加 checkMobileFn() 进行手机号检查
methods: {
async checkUsernameFn() {
//检查用户名
let {data} = await this.KaTeX parse error: Expected 'EOF', got '}' at position 88: …ata = data }̲, async che…request.checkMobile( this.user.mobile )
this.userMsg.mobileData = data
}
},
步骤三:编写需要的2个变量
data() {
return {
user : { //表单封装数据
username : “”,
mobile : “”
},
userMsg : { //错误提示数据
usernameData : “”,
mobileData : “”
}
}
},
步骤四:处理页面
完整版
用户注册
4.4用户注册:前置技术–Redis
4.5用户注册:前置技术–阿里大鱼
4.6用户注册:短信验证码
4.6.1分析4.6.2接口
http://localhost:10010/web-service/sms
{
“mobile”:“13344445555”,
“username”: “jack”
}4.6.3后端
创建 SmsController类,调用阿里大鱼工具类,发送短信。package com.czxy.changgou4.controller;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.czxy.changgou4.pojo.User;
import com.czxy.changgou4.utils.SmsUtil;
import com.czxy.changgou4.vo.BaseResult;
import org.apache.commons.lang.RandomStringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;/**
-
Created by liangtong.
*/
@RestController
@RequestMapping(“/sms”)
public class SmsController {@Resource
private StringRedisTemplate redisTemplate;@PostMapping
public BaseResult sendSms(@RequestBody User user){
long start = System.currentTimeMillis();
try {
//发送短信
//1 生产验证码
String code = RandomStringUtils.randomNumeric(4);
System.out.println(“验证码:” + code);//2 并存放到reids中 , key: "sms_register" + 手机号 , value:验证码 , 1小时 redisTemplate.opsForValue().set( "sms_register" + user.getMobile() , code , 1 , TimeUnit.HOURS); /**/ //3 发送短信 SendSmsResponse smsResponse = SmsUtil.sendSms(user.getMobile(), user.getUsername() , code, "", ""); //https://help.aliyun.com/document_detail/55284.html?spm=5176.doc55322.6.557.KvvIJx if("OK".equalsIgnoreCase(smsResponse.getCode())){ return BaseResult.ok("发送成功"); } else { return BaseResult.error(smsResponse.getMessage()); } /* //模拟数据 System.out.println("验证码:" + code); return BaseResult.ok("发送成功"); */ } catch (Exception e) { long end = System.currentTimeMillis(); System.out.println( end - start); return BaseResult.error("发送失败" ); }
}
}
4.6.4前端
步骤一:修改apiclient.js,发送短信ajax操作//发短信
sendSms : ( user )=> {
return axios.post(‘/web-service/sms’, user )
}步骤二:修改Register.vue页面,给“发送验证码”绑定点击事件 sendSmsFn
<button @click.prevent=“sendSmsFn” >
发送验证码5秒
步骤三:修改Register.vue页面,编写sendSmsFn函数,建议采用 ajax…then()…catch 可以处理异常
sendSmsFn () {
this.$request.sendSms( this.user )
.then(( response )=>{
//发送短信的提示信息
this.userMsg.smsData = response.data
})
.catch(( error )=>{
//错误提示信息
alert( error.message )
})
}步骤四:修改Register.vue页面,提供变量smsData
userMsg : { //错误提示数据
usernameData : “”,
mobileData : “”,
smsData : “”
}步骤五:修改Register.vue页面,显示 smsData提示信息
{{userMsg.smsData.message}}
4.6.5倒计时
步骤一:提供3个变量,用于控制倒计时btnDisabled : false, //倒计时控制变量
seconds : 5, //默认倒计时秒数
timer : null, //接收定时器,清除定时器步骤二:在标签上面控制倒计时的显示
<button :disabled=“btnDisabled” @click.prevent=“sendSmsFn” >
发送验证码{{seconds}}秒
步骤三:发送短信后,开启倒计时控制
sendSmsFn () {
this.$request.sendSms( this.user )
.then(( response )=>{
//发送短信的提示信息
this.userMsg.smsData = response.data
//按钮不可用
this.btnDisabled = true;
//倒计时
this.timer = setInterval( ()=>{
if(this.seconds <= 1){
//结束
// 重置秒数
this.seconds = 5;
// 按钮可用
this.btnDisabled = false;
// 停止定时器
clearInterval(this.timer);
} else {
this.seconds --;
}
} , 1000);
})
.catch(( error )=>{
//错误提示信息
alert( error.message )
})
}4.7用户注册
4.7.1接口
http://localhost:10010/web-service/user/register
{
“mobile”:“13612345677”,
“password”:“1234”,
“username”:“jack3”,
“code”:“3919”
}4.7.2后端
保存前需要再次进行服务端校验
用户名是否注册
手机号是否注册
验证码是否失效
验证码是否错误
密码需要使用 BCrypt进行加密步骤一:修改UserService接口,添加register方法
/**- 用户注册
- @param user
- @return
*/
public boolean register(User user) ;
步骤二:完善UserServiceImpl实现类
@Override
public boolean register(User user) {
//密码加密
String newPassword = BCrypt.hashpw(user.getPassword());
user.setPassword(newPassword);//处理数据 user.setCreatedAt(new Date()); user.setUpdatedAt(user.getCreatedAt()); int insert = baseMapper.insert(user); return insert == 1;
}
步骤三:修改UserController,添加register方法
/**
-
用户注册
-
@param user
-
@return
*/
@PostMapping(“/register”)
public BaseResult register(@RequestBody User user){//服务端校验
User findUser = userService.findByUsername(user.getUsername());
if(findUser != null) {
return BaseResult.error(“用户名已经存在”);
}findUser = userService.findByMobile(user.getMobile());
if(findUser != null) {
return BaseResult.error(“电话号码已经存在”);
}//验证码
String code = stringRedisTemplate.opsForValue().get(“sms_register” + user.getMobile());
//删除redis中的验证码
stringRedisTemplate.delete(“sms_register” + user.getMobile());
if(code == null) {
return BaseResult.error(“验证码失效”);
}
if(!code.equals(user.getCode())) {
return BaseResult.error(“验证码不正确”);
}//注册
boolean register = userService.register(user);if(register) {
return BaseResult.ok(“注册成功”);
}
return BaseResult.error(“注册失败”);
}
4.7.3日期处理(可选)
编写 DateMetaObjectHandler 用于处理“创建时间”和“修改日期”
package com.czxy.changgou4.handler;import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;import java.util.Date;
/**
-
@author 桐叔
-
@email liangtong@itcast.cn
*/
@Component
public class DateMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName(“createdAt”, new Date(), metaObject);
this.setFieldValByName(“updatedAt”, new Date(), metaObject);
}@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName(“updatedAt”, new Date(), metaObject);
}
}
完善User JavaBean,设置填充方式
@TableField(value=“created_at”,fill = FieldFill.INSERT)
private Date createdAt;@TableField(value=“updated_at”,fill = FieldFill.INSERT_UPDATE)
private Date updatedAt;4.7.4前端
步骤一:修改 api.js ,添加注册函数//注册
register : ( user )=> {
return axios.post(‘/web-service/user/register’, user )
}步骤二:处理表单,验证码input绑定数据,提交按钮绑定事件
<button :disabled=“btnDisabled” @click.prevent=“sendSmsFn” >
发送验证码{{seconds}}秒
{{userMsg.smsData.message}}
我已阅读并同意《用户注册协议》
<input type=“submit” value=“” @click.prevent=“registerFn” class=“login_btn” />
-
步骤三:完善data区域的user数据
user : { //表单封装数据
username : “”, //用户名
mobile : “13699282444”, //手机号
password : “”, //密码
code : “” //验证码
},步骤四:编写registerFn函数
async registerFn() {
let { data } = await this.KaTeX parse error: Expected '}', got 'EOF' at end of input: …功 this.router.push(‘/login’)
} else {
//失败–与发送验证码使用一个位置显示错误信息
this.userMsg.smsData = data
}
}4.8用户登录
4.8.1构建页面:Login.vue
步骤一:创建Login.vue步骤二:绘制通用模块
<div style="clear:both;"></div> <Footer></Footer>
步骤三:绘制登录表单
用户登录
- 忘记密码?
- 看不清?换一张
- 保存登录信息
<div class="coagent mt15"> <dl> <dt>使用合作网站登录商城:</dt> <dd class="qq"><a href=""><span></span>QQ</a></dd> <dd class="weibo"><a href=""><span></span>新浪微博</a></dd> <dd class="yi"><a href=""><span></span>网易</a></dd> <dd class="renren"><a href=""><span></span>人人</a></dd> <dd class="qihu"><a href=""><span></span>奇虎360</a></dd> <dd class=""><a href=""><span></span>百度</a></dd> <dd class="douban"><a href=""><span></span>豆瓣</a></dd> </dl> </div> </div> <div class="guide fl"> <h3>还不是商城用户</h3> <p>现在免费注册成为商城用户,便能立刻享受便宜又放心的购物乐趣,心动不如行动,赶紧加入吧!</p> <a href="regist.html" class="reg_btn">免费注册 >></a> </div> </div> </div> <!-- 登录主体部分end --> <div style="clear:both;"></div> <Footer></Footer>
4.8.2分析
4.8.3验证码:接口
http://localhost:10010/web-service/verifycode?username=jack4.8.4验证码:生成与显示
步骤一:后端生产验证码,并将用户保存Redis
存放redis中验证码key格式:“login” + 用户名package com.czxy.changgou4.controller;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;import javax.annotation.Resource;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.TimeUnit;/**
-
@author 桐叔
-
@email liangtong@itcast.cn
*/
@Controller
@RequestMapping(“/verifycode”)
public class VerifyCodeController {@Resource
private StringRedisTemplate stringRedisTemplate;@GetMapping
public void verifyCode(String username , HttpServletResponse response ) throws IOException {//字体只显示大写,去掉了1,0,i,o几个容易混淆的字符 String VERIFY_CODES = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"; int IMG_WIDTH = 72; int IMG_HEIGTH = 27; Random random = new Random(); //创建图片 BufferedImage image = new BufferedImage(IMG_WIDTH, IMG_HEIGTH, BufferedImage.TYPE_INT_RGB); //画板 Graphics g = image.getGraphics(); //填充背景 g.setColor(Color.WHITE); g.fillRect(1,1,IMG_WIDTH-2,IMG_HEIGTH-2); g.setFont(new Font("楷体", Font.BOLD,25)); StringBuilder sb = new StringBuilder(); //写字 for(int i = 1 ; i <= 4 ; i ++){ //随机颜色 g.setColor(new Color(random.nextInt(255),random.nextInt(255),random.nextInt(255))); int len = random.nextInt(VERIFY_CODES.length()); String str = VERIFY_CODES.substring(len,len+1); sb.append(str); g.drawString(str, IMG_WIDTH / 6 * i , 22 ); } //将验证码存放到redis stringRedisTemplate.opsForValue().set( "login" + username , sb.toString() , 1 , TimeUnit.HOURS); // 生成随机干扰线 for (int i = 0; i < 30; i++) { //随机颜色 g.setColor(new Color(random.nextInt(255),random.nextInt(255),random.nextInt(255))); int x = random.nextInt(IMG_WIDTH - 1); int y = random.nextInt(IMG_HEIGTH - 1); int x1 = random.nextInt(12) + 1; int y1 = random.nextInt(6) + 1; g.drawLine(x, y, x - x1, y - y1); } //响应到浏览器 ImageIO.write(image,"jpeg", response.getOutputStream());
}
}
步骤二:点击“换一张”显示验证码
默认不显示验证码
点击“换一张”获得验证码用户登录
- 忘记密码?
- 看不清?
4.8.5通过用户名查询:接口
http://localhost:10010/web-service/user/findByUsername
{
“username”:“jack”
}4.8.6通过用户名查询:实现
修改UserController,添加 findByUsername函数/**
- 通过用户名查询
- @param user
- @return 返回用户对象
*/
@PostMapping(“/findByUsername”)
public User findByUsername(@RequestBody User user){
//查询用户
User findUser = userService.findByUsername( user.getUsername() );
return findUser;
}
4.8.7认证服务:构建项目(changgou4-service-auth)
步骤一:构建项目步骤二:创建pom.xml文件
com.czxy.changgou changgou4_common_auth<!--web起步依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- nacos 客户端 --> <dependency> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-client</artifactId> </dependency> <!-- nacos 服务发现 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!--redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> <!--swagger2--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> </dependency>
步骤三:创建yml文件
server:
port: 8085
spring:
application:
name: auth-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 #nacos服务地址sc:
jwt:
secret: sc@Login(Auth}*^31)&czxy% # 登录校验的密钥
pubKeyPath: D:/rsa/rsa.pub # 公钥地址
priKeyPath: D:/rsa/rsa.pri # 私钥地址
expire: 360 # 过期时间,单位分钟步骤四:配置启动类
package com.czxy.changgou4;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;/**
- @author 桐叔
- @email liangtong@itcast.cn
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class CGAuthServiceApplication {
public static void main(String[] args) {
SpringApplication.run(CGAuthServiceApplication.class, args);
}
}
步骤五:配置类
4.8.8认证服务:用户登录后端
步骤一:创建AuthUser 封装对象(与User比较,缺数据库相关注解)
package com.czxy.changgou4.domain;import lombok.Data;
import java.util.Date;
/**
-
@author 桐叔
-
@email liangtong@itcast.cn
*/
@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 liangtong@itcast.cn
*/
@FeignClient(value = “web-service”,path=“/user”)
public interface UserFeign {@PostMapping(“/findByUsername”)
public AuthUser findByUsername(@RequestBody AuthUser user);
}
步骤三:创建AuthService接口,编写登录方法 login()
package com.czxy.changgou4.service;import com.czxy.changgou4.domain.AuthUser;
/**
-
@author 桐叔
-
@email liangtong@itcast.cn
*/
public interface AuthService {/**
- 用户登录
- @param user
- @return
*/
public AuthUser login(AuthUser user ) ;
}
步骤四:创建AuthService实现类,并通过BCrypt校验密码
package com.czxy.changgou4.service.impl;
import com.czxy.changgou4.domain.AuthUser;
import com.czxy.changgou4.feign.UserFeign;
import com.czxy.changgou4.service.AuthService;
import com.czxy.changgou4.utils.BCrypt;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
/**
-
@author 桐叔
-
@email liangtong@itcast.cn
*/
@Service
public class AuthServiceImpl implements AuthService {
@Resource
private UserFeign userFeign;/**
- 用户登录
- @param user
- @return
*/
public AuthUser login(AuthUser user ) {
//远程查询用户
AuthUser findUser = userFeign.findByUsername(user);
if(findUser == null) {
return null;
}
//校验密码是否正确
boolean checkpw = BCrypt.checkpw( user.getPassword(), findUser.getPassword());
if(checkpw){
return findUser;
}
return null;
}
}
步骤五:创建AuthController,添加login方法
redis中登录验证码和用户输入的验证码进行匹配package com.czxy.changgou4.controller;
/**
- @author 桐叔
- @email liangtong@itcast.cn
*/
import com.czxy.changgou4.domain.AuthUser;
import com.czxy.changgou4.service.AuthService;
import com.czxy.changgou4.vo.BaseResult;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;
/**
-
Created by liangtong.
*/
@RestController
@RequestMapping(“/auth”)
public class AuthController {@Resource
private AuthService authService;@Resource
private StringRedisTemplate stringRedisTemplate;@PostMapping(“/login”)
public BaseResult login(@RequestBody AuthUser user){
//校验验证码–使用后删除
String redisCode = stringRedisTemplate.opsForValue().get( “login” + user.getUsername() );
stringRedisTemplate.delete( “login” + user.getUsername() );
if(redisCode == null) {
return BaseResult.error(“验证码无效”);
}
if(! redisCode.equalsIgnoreCase(user.getCode())) {
return BaseResult.error(“验证码错误”);
}
//登录
AuthUser loginUser = authService.login(user);
if(loginUser != null ) {
return BaseResult.ok(“登录成功”).append(“loginUser”,loginUser);
} else {
return BaseResult.error(“用户名或密码不匹配”);
}
}
}
4.8.9认证服务:用户登录前端
步骤一:修改apiclient.js,添加login函数
//登录
login : ( user )=> {
return axios.post(‘/auth-service/auth/login’, user )
}步骤二:修改Login.vue,给验证码绑定变量
步骤三:修改Login.vue,给提交按钮绑定事件
步骤四:编写loginFn完成登录功能
登录成功,跳转到首页
登录失败,给出提示
async loginFn() {
let { data } = await this.KaTeX parse error: Expected '}', got 'EOF' at end of input: …页 this.router.push(‘/’)
} else {
this.errorMsg = data.message
}
}步骤五:创建首页 ~/pages/index.vue,
步骤六:重命名静态页面 ~/static/index.html 为 ~/static/home.html
4.8.10修改 TopNav.vue 组件
完善导航条,根据vuex中的数据,显示不同内容
步骤一:创建 ~/store/index.js ,并编写vuex内容
export const state = () => ({
user: null
})//通用设置
export const mutations = {
setData( state , obj) {
state[obj.key] = obj.value
}
}步骤二:页面登录成功,将用户信息保存到vuex中
// 将用户信息保存到vuex中
this.$store.commit(‘setData’, {key:‘user’,value: data.data })步骤三:修改顶部导航TopNav.vue
从vuex中的数据
- 您好,{{user.username}} 欢迎来到畅购! <a href=“” @click.prevent=“logout”>退出
- |
- [登录] [免费注册]
- |
- 我的订单
- |
- 客户服务
4.8.11vuex刷新数据丢失
刷新操作:
点击刷新按钮
点击回退按钮
地址栏直接输入地址
现象:
vuex在刷新操作后,数据丢失了
解决方案
方案1:不是公共组件:页面在pages目录下,可以nuxt.js提供 fetch进行操作。
方案2:是公共组件:组件在components目录下,借助第三方进行存储(cookie、localStorage、sessionStorage)
具体操作:
如果vuex中没有数据,使用sessionStorage的数据填充vuex。
修改TopNav.vue页面
- 您好,{{user.username}}欢迎来到畅购!
[登录]
[免费注册]
[退出]
- |
- 我的订单
- |
- 客户服务
4.9整合JWT
生成token:在用户登录成功,根据用户的登录信息,生成登录标识token,并返回给浏览器。
使用token:完善ajax请求,在请求之前添加请求头,设置token
校验token:在网关中编写过滤器,进行请求进行拦截,并校验token。
白名单:在白名单中的请求,是不需要token可以直接访问的。
4.9.1生成Token
用户登录成功,生成token,并将token响应给浏览器。(认证服务 AuthService)
步骤一:查看 application.yml文件,确定 jwt配置信息步骤二:创建JwtProperties文件,用于加载sc.jwt配置信息
package com.czxy.changgou4.config;
import com.czxy.changgou4.utils.RsaUtils;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;
import java.io.File;
import java.security.PrivateKey;
import java.security.PublicKey;/**
-
@author 桐叔
-
@email liangtong@itcast.cn
*/
@Data
@ConfigurationProperties(prefix = “sc.jwt”)
@Component
public class JwtProperties {private String secret; // 密钥
private String pubKeyPath;// 公钥
private String priKeyPath;// 私钥
private int expire;// token过期时间
private PublicKey publicKey; // 公钥
private PrivateKey privateKey; // 私钥
private static final Logger logger = LoggerFactory.getLogger(JwtProperties.class);
@PostConstruct
public void init(){
try {
File pubFile = new File(this.pubKeyPath);
File priFile = new File(this.priKeyPath);
if( !pubFile.exists() || !priFile.exists()){
RsaUtils.generateKey( this.pubKeyPath ,this.priKeyPath , this.secret);
}
this.publicKey = RsaUtils.getPublicKey( this.pubKeyPath );
this.privateKey = RsaUtils.getPrivateKey( this.priKeyPath );
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
}
步骤三:修改AuthController,注入JwtProperties,并使用JwtUtils生成token
package com.czxy.changgou4.controller;/**
- @author 桐叔
- @email liangtong@itcast.cn
*/
import com.czxy.changgou4.config.JwtProperties;
import com.czxy.changgou4.domain.AuthUser;
import com.czxy.changgou4.service.AuthService;
import com.czxy.changgou4.utils.JwtUtils;
import com.czxy.changgou4.vo.BaseResult;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;
/**
-
Created by liangtong.
*/
@RestController
@RequestMapping(“/auth”)
public class AuthController {@Resource
private AuthService authService;@Resource
private StringRedisTemplate stringRedisTemplate;@Resource
private JwtProperties jwtProperties;@PostMapping(“/login”)
public BaseResult login(@RequestBody AuthUser user){
//校验验证码–使用后删除
String redisCode = stringRedisTemplate.opsForValue().get( “login” + user.getUsername() );
stringRedisTemplate.delete( “login” + user.getUsername() );
if(redisCode == null) {
return BaseResult.error(“验证码无效”);
}
if(! redisCode.equalsIgnoreCase(user.getCode())) {
return BaseResult.error(“验证码错误”);
}
//登录
AuthUser loginUser = authService.login(user);
if(loginUser != null ) {
//生成Token
String token = JwtUtils.generateToken(loginUser, jwtProperties.getExpire(), jwtProperties.getPrivateKey());return BaseResult.ok("登录成功").append("loginUser",loginUser).append("token", token); } else { return BaseResult.error("用户名或密码不匹配"); }
}
}
4.9.2使用token
步骤一:登录成功后保存token,修改 Login.vue页面async loginFn() { let { data } = await this.$request.login( this.user ) if( data.code == 20000) { //成功 sessionStorage.setItem('user' , JSON.stringify(data.other.loginUser) ) //保存token sessionStorage.setItem('token' , data.other.token ) //跳转到首页 this.$router.push('/') } else { this.errorMsg = data.message } }
步骤二:请求是自动携带token,修改apiclient.js,将token添加到请求头
//参考 https://axios.nuxtjs.org/helpers
let token = sessionStorage.getItem(‘token’)
if( token ) {
// Adds header:Authorization: 123
to all requests
// this.$axios.setToken(‘123’)
$axios.setToken( token )
}步骤三:检查 nuxt.conf.js,插件模式改成“client”
否则抛异常“sessionStorage is not defined”
plugins: [
{ src: ‘~plugins/apiclient.js’, mode: ‘client’ }
],4.9.3校验token
token的校验在网关项目处完成
步骤一:修改application.yml添加jwt配置#自定义内容
sc:
jwt:
secret: sc@Login(Auth}*^31)&czxy% # 登录校验的密钥
pubKeyPath: D:/rsa/rsa.pub # 公钥地址
priKeyPath: D:/rsa/rsa.pri # 私钥地址
expire: 360 # 过期时间,单位分钟步骤二:创建 JwtProperties,用于加载配置文件
package com.czxy.changgou4.config;
import com.czxy.changgou4.utils.RsaUtils;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;
import java.io.File;
import java.security.PrivateKey;
import java.security.PublicKey;/**
-
@author 桐叔
-
@email liangtong@itcast.cn
*/
@Data
@ConfigurationProperties(prefix = “sc.jwt”)
public class JwtProperties {private String secret; // 密钥
private String pubKeyPath;// 公钥
private String priKeyPath;// 私钥
private int expire;// token过期时间
private PublicKey publicKey; // 公钥
private PrivateKey privateKey; // 私钥
private static final Logger logger = LoggerFactory.getLogger(JwtProperties.class);
@PostConstruct
public void init(){
try {
File pubFile = new File(this.pubKeyPath);
File priFile = new File(this.priKeyPath);
if( !pubFile.exists() || !priFile.exists()){
RsaUtils.generateKey( this.pubKeyPath ,this.priKeyPath , this.secret);
}
this.publicKey = RsaUtils.getPublicKey( this.pubKeyPath );
this.privateKey = RsaUtils.getPrivateKey( this.priKeyPath );
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
}
步骤三:编写过滤器,对所有路径进行拦截
package com.czxy.changgou4.filter;
import com.czxy.changgou4.config.FilterProperties;
import com.czxy.changgou4.config.JwtProperties;
import com.czxy.changgou4.pojo.User;
import com.czxy.changgou4.utils.JwtUtils;
import com.czxy.changgou4.utils.RsaUtils;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;/**
-
@author 桐叔
-
@email liangtong@itcast.cn
*/
@Component
public class LoginFilter implements GlobalFilter, Ordered {@Resource
private JwtProperties jwtProperties;@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1 获得请求路径
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
System.out.println(path);//2 白名单放行 //3 获得token String token = request.getHeaders().getFirst("Authorization"); //4 校验token try { JwtUtils.getObjectFromToken(token, RsaUtils.getPublicKey(jwtProperties.getPubKeyPath()), User.class); return chain.filter(exchange); } catch (Exception e) { ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(HttpStatus.UNAUTHORIZED); response.getHeaders().add("Content-Type","application/json;charset=UTF-8"); DataBuffer wrap = response.bufferFactory().wrap("没有权限".getBytes(StandardCharsets.UTF_8)); return exchange.getResponse().writeWith(Flux.just(wrap)); }
}
@Override
public int getOrder() {
return 1;
}
}
步骤四:修改前端 apiclient.js 文件,用于处理401异常
//处理响应异常
$axios.onError(error => {
// token失效,服务器响应401
if(error.response.status === 401) {
console.error(error.response.data)
redirect(‘/login’)
}
})api.js 完整代码
var axios = null
export default ({ $axios, redirect, process }, inject) => {//参考 https://axios.nuxtjs.org/helpers
let token = sessionStorage.getItem(‘token’)
if( token ) {
// Adds header:Authorization: 123
to all requests
// this.$axios.setToken(‘123’)
$axios.setToken( token )
}//处理响应异常
$axios.onError(error => {
// token失效,服务器响应401
if(error.response.status === 401) {
console.error(error.response.data)
redirect(‘/login’)
}
})//赋值
axios = $axios//4) 将自定义函数交于nuxt
// 使用方式1:在vue中,this. r e q u e s t . x x x ( ) / / 使用方式 2 :在 n u x t 的 a s y n c D a t a 中, c o n t e n t . a p p . request.xxx() // 使用方式2:在nuxt的asyncData中,content.app. request.xxx()//使用方式2:在nuxt的asyncData中,content.app.request.xxx()
inject(‘request’, request)
}4.9.4白名单
不需要拦截的资源都配置到yml文件中,在过滤器直接放行
步骤一:修改application.yml文件#自定义内容
sc:
jwt:
secret: sc@Login(Auth}*^31)&czxy% # 登录校验的密钥
pubKeyPath: D:/rsa/rsa.pub # 公钥地址
priKeyPath: D:/rsa/rsa.pri # 私钥地址
expire: 360 # 过期时间,单位分钟
filter:
allowPaths:
- /checkusername
- /checkmobile
- /sms
- /register
- /login- /verifycode
- /categorys
- /news
- /brands
- /specifications
- /search
- /goods
- /comments
- swagger
- /api-docs
步骤二:创建FilterProperties配置文件,用于存放允许放行的路径
package com.czxy.changgou4.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;import java.util.List;
/**
-
@author 桐叔
-
@email liangtong@itcast.cn
*/
@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 liangtong@itcast.cn
*/
@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=desc5.2.2后端实现:JavaBean
步骤一:修改pojo项目,创建分页基类 PageRequest
package com.czxy.changgou4.vo;import lombok.Data;
/**
- @author 桐叔
- @email liangtong@itcast.cn
*/
@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 liangtong@itcast.cn
*/
@Data
public class NewsVo extends PageRequest {
}
步骤三:根据数据库表创建News对象
package com.czxy.changgou4.pojo;import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;import java.util.Date;
/**
-
@author 桐叔
-
@email liangtong@itcast.cn
*/
@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 liangtong@itcast.cn
*/
@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 liangtong@itcast.cn
*/
public interface NewsService extends IService {/**
- 查询所有新闻
- @param newsVo
- @return
*/
public Page findAll(NewsVo newsVo);
}
步骤三:创建service实现类,按照创建时间,根据排序方式进行排序
package com.czxy.changgou4.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.czxy.changgou4.mapper.NewsMapper;
import com.czxy.changgou4.pojo.News;
import com.czxy.changgou4.service.NewsService;
import com.czxy.changgou4.vo.NewsVo;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;/**
-
@author 桐叔
-
@email liangtong@itcast.cn
*/
@Service
@Transactional
public class NewsServiceImpl extends ServiceImpl<NewsMapper, News> implements NewsService {
@Override
public Page findAll(NewsVo newsVo) {
//1 条件
QueryWrapper queryWrapper = new QueryWrapper();
if(“asc”.equals(newsVo.getSortWay())) {
queryWrapper.orderByAsc(“created_at”);
} else {
queryWrapper.orderByDesc(“created_at”);
}//2 分页 Page<News> page = new Page<>(newsVo.getPageNum(), newsVo.getPageSize()); //3 查询 baseMapper.selectPage(page, queryWrapper); return page;
}
}
步骤四:修改controller
package com.czxy.changgou4.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.czxy.changgou4.pojo.News;
import com.czxy.changgou4.service.NewsService;
import com.czxy.changgou4.vo.BaseResult;
import com.czxy.changgou4.vo.NewsVo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;
/**
-
@author 桐叔
-
@email liangtong@itcast.cn
*/
@RestController
@RequestMapping(“/news”)
public class NewsController {@Resource
private NewsService newsService;@GetMapping
public BaseResult findAll(NewsVo newsVo){
//1 查询
Page page = this.newsService.findAll( newsVo );
//2 封装
return BaseResult.ok(“成功”, page);
}
}
5.2.4前端实现
步骤一:编写服务端apiserver.js步骤二:编写服务端apiserver.js,修改nuxt.config.js 配置仅服务器可用
plugins: [
{ src: ‘~plugins/apiclient.js’,mode: ‘client’},
{ src: ‘~plugins/apiserver.js’},
],步骤三:修改“apiserver.js”,查询快报
const request = {
//快报
findNews : () => {
return axios.get(‘/web-service/news’ , {
params : {
pageNum : 1,
pageSize : 8 ,
sortWay : ‘asc’
}
})
}
}var axios = null
export default ({ $axios, redirect, process }, inject) => {//赋值
axios = $axios//4) 将自定义函数交于nuxt
// 使用方式1:在vue中,this. r e q u e s t . x x x ( ) / / 使用方式 2 :在 n u x t 的 a s y n c D a t a 中, c o n t e n t . a p p . request.xxx() // 使用方式2:在nuxt的asyncData中,content.app. request.xxx()//使用方式2:在nuxt的asyncData中,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 liangtong@itcast.cn
*/
@TableName(“tb_category”)
@Data
public class Category {@TableId
private Integer id;@TableField(value = “cat_name”)
@JsonProperty(“cat_name”)
private String catName;@TableField(value = “parent_id”)
@JsonProperty(“parent_id”)
private Integer parentId;@TableField(value = “is_parent”)
@JsonProperty(“is_parent”)
private Boolean isParent;//当前分类具有的所有孩子
@TableField(exist = false)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private List children = new ArrayList<>();
}
5.3.3后端实现:查询
步骤一:创建mapper
package com.czxy.changgou4.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.czxy.changgou4.pojo.Category;
import org.apache.ibatis.annotations.Mapper;/**
- @author 桐叔
- @email liangtong@itcast.cn
*/
@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 liangtong@itcast.cn
/
public interface CategoryService extends IService {
/*- 查询所有一级分类,每一个分类中含子分类
- @return
*/
public List findAll();
}
步骤三:创建service实现类,按照parentId升序排序,并将所有的分类进行按照级别进行处理。
package com.czxy.changgou4.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.czxy.changgou4.mapper.CategoryMapper;
import com.czxy.changgou4.pojo.Category;
import com.czxy.changgou4.service.CategoryService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/**
-
@author 桐叔
-
@email liangtong@itcast.cn
*/
@Service
@Transactional
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
@Override
public List findAll() {
//1 条件
QueryWrapper queryWrapper = new QueryWrapper<>();
queryWrapper.orderByAsc(“parent_id”);//2 查询所有 List<Category> findList = baseMapper.selectList(queryWrapper); //2 存放所有的一级分类 List<Category> list = new ArrayList<>(); // 3.1 存放每一个分类(此分类将来要做父分类的) Map<Integer , Category> cache = new HashMap<>(); for (Category category : findList) { //2.1 过滤所有一级 parentId==0 if(category.getParentId() == 0){ list.add( category ); } //3.2 向map存一份 cache.put( category.getId() , category ); //3.3 获得当前父分类 Category parentCategory = cache.get( category.getParentId() ); if( parentCategory != null) { parentCategory.getChildren().add( category ); } } //2.2 返回处理结果 return list;
}
}
步骤四:创建controller
package com.czxy.changgou4.controller;
import com.czxy.changgou4.pojo.Category;
import com.czxy.changgou4.service.CategoryService;
import com.czxy.changgou4.vo.BaseResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;
import java.util.List;/**
-
@author 桐叔
-
@email liangtong@itcast.cn
*/
@RestController
@RequestMapping(“/categorys”)
public class CategoryController {@Resource
private CategoryService categoryService;@GetMapping
public BaseResult findAll(){
//1 查询
List list = categoryService.findAll();
//2 封装
return BaseResult.ok(“查询成功” , list );
}
}
5.3.4前端实现
步骤一:修改“apiserver.js”,查询所有分类//查询所有分类
findCategorys : () => {
return axios.get(“/web-service/categorys”)
}步骤二:修改index.vue,使用asyncData查询分类
asyncData函数只能在 pages 目录下使用async asyncData( { app } ) {
// 查询快报 + 查询分类
let [{data:newsData}, {data: categoryData}] = await Promise.all([
app. r e q u e s t S e r v e r . f i n d N e w s ( ) , a p p . requestServer.findNews(), app. requestServer.findNews(),app.requestServer.findCategorys()
])
return {
newsList : newsData.data.records,
categorysList : categoryData.data
}},
步骤三:修改index.vue,将查询的结果,通过属性传递给组件
<!-- 头部 start --> <HeaderSearch :list="categorysList"></HeaderSearch> <!-- 头部 end-->
步骤四:修改组件,声明list属性
export default {
props: {
isFirst: { //是否是首页
type: Boolean,
default: true
},
list: { //数据
type: Array
}}
}步骤五:遍历数据
{{c1.cat_name}}
-
{{c2.cat_name}}
- {{c3.cat_name}}
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/766.3.2后端实现:JavaBean
表结构分析:
分类表:tb_category
品牌表:tb_brand
关系:多对多(不同的分类,拥有不同的品牌)tb_category_brandpackage 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 liangtong@itcast.cn
*/
@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 liangtong@itcast.cn
*/
@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 liangtong@itcast.cn
/
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 liangtong@itcast.cn
*/
@Service
@Transactional
public class BrandServiceImpl extends ServiceImpl<BrandMapper, Brand> implements BrandService {@Override
public List findAll(Integer categoryId) {
return baseMapper.findAll(categoryId);
}
}
步骤四:创建BrandController,符合接口规范
package com.czxy.changgou4.controller;import com.czxy.changgou4.pojo.Brand;
import com.czxy.changgou4.service.BrandService;
import com.czxy.changgou4.vo.BaseResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;
import java.util.List;/**
-
@author 桐叔
-
@email liangtong@itcast.cn
*/
@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/766.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 liangtong@itcast.cn
/
@TableName(“tb_specification_option”)
@Data
public class SpecificationOption {
/
CREATE TABLEtb_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
),
KEYspecification_options_spec_id_index
(spec_id
)
)
*/
@TableId(type = IdType.AUTO)
private Integer id;@TableField(value=“spec_id”)
@JsonProperty(“spec_id”)
private Integer specId; //外键,规格IDprivate 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 liangtong@itcast.cn
/
@TableName(“tb_specification”)
@Data
public class Specification {
/
CREATE TABLEtb_specification
(
id
int(10) unsigned NOT NULL AUTO_INCREMENT,
spec_name
varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT ‘规格名称’,
category_id
int(10) unsigned NOT NULL COMMENT ‘分类ID’,
PRIMARY KEY (id
)
)
*/
@TableId(type = IdType.AUTO)
private Integer id;@TableField(value = “spec_name”)
@JsonProperty(“spec_name”)
private String specName; //规格名称@TableField(value = “category_id”)
private Integer categoryId; //分类外键
private Category category; //分类外键对应对象private List options; //一个规格,具有多个规格选项
}
6.4.3后端实现:查询
查询规格的同时,查询对应的规格选项步骤一:创建 SpecificationOptionMapper,用于查询指定“规格”的所有“规格选项”
package com.czxy.changgou4.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.czxy.changgou4.pojo.SpecificationOption;
import org.apache.ibatis.annotations.*;import java.util.List;
/**
-
@author 桐叔
-
@email liangtong@itcast.cn
*/
@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 liangtong@itcast.cn
/
@Mapper
public interface SpecificationMapper extends BaseMapper {
/*- 查询指定分类的所有规格
- @param categoryId
- @return
*/
@Select(“select * from tb_specification where category_id = #{categoryId}”)
@Results({
@Result(property = “id”, column = “id”),
@Result(property = “specName”, column = “spec_name”),
@Result(property = “categoryId”, column = “category_id”),
@Result(property = “options”, many=@Many(select=“com.czxy.changgou4.mapper.SpecificationOptionMapper.findSpecOptionBySpecId”), column = “id”),
})
public List findSpecificationByCategoryId(@Param(“categoryId”) Integer categoryId);
}
步骤三:创建SpecificationService接口
package com.czxy.changgou4.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.czxy.changgou4.pojo.Specification;import java.util.List;
/**
- @author 桐叔
- @email liangtong@itcast.cn
/
public interface SpecificationService extends IService {
/*- 查询指定分类的所有规格
- @param categoryId
- @return
*/
public List findSpecificationByCategoryId(Integer categoryId);
}
步骤四:创建SpecificationService实现类
package com.czxy.changgou4.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.czxy.changgou4.mapper.SpecificationMapper;
import com.czxy.changgou4.pojo.Specification;
import com.czxy.changgou4.service.SpecificationService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.util.List;
/**
- @author 桐叔
- @email liangtong@itcast.cn
*/
@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 liangtong@itcast.cn
*/
@RestController
@RequestMapping(“/specifications”)
public class SpecificationController {
@Resource
private SpecificationService specificationService;/**
- GET /specifications/category/:catid
- @param categoryId
- @return
*/
@GetMapping(“/category/{catId}”)
public BaseResult findSpecificationByCategoryId(@PathVariable(“catId”) Integer categoryId){
//1 查询
List list = this.specificationService.findSpecificationByCategoryId(categoryId);
//2 封装
return BaseResult.ok(“查询成功”, list);
}
}
6.4.4前端实现
步骤一:修改 api.js ,查询规格//指定分类的规格
findSpec : (categoryId) => {
return axios.get(‘/web-service/specifications/category/’ + categoryId)
},步骤二:修改 pages/list/_id ,页面加载成功,查询规格
data() {
return {
searchMap: { //搜索条件对象
catId: ‘’, //分类id
brandId: ‘’, // 品牌id
},
brandList : [], //所有品牌
specList : [], //所有规格}
},
methods: {
async findAllBrandFn() {
let { data : brandData } = await this.KaTeX parse error: Expected 'EOF', got '}' at position 85: …dData.data }̲, async fin…request.findSpec( this.searchMap.catId )
this.specList = specData.data
},
brandSearch(bid) {
//记录品牌id
this.searchMap.brandId = bid;//查询 this.searchList(); }, searchList () { console.info(this.searchMap) }
},
async mounted() {//设置id this.searchMap.catId = this.$route.params.cid // 查询所有的品牌 this.findAllBrandFn() // 查询所有的规格 this.findAllSpecFn()
},
步骤三:显示数据
<dl v-for="(sl,sli) in specList" :key="sli"> <dt>{{sl.spec_name}}:</dt> <dd :class="{'cur': sl.selectId == ''}"><a href="" @click.prevent="specSearch(sl ,'' , )">不限</a></dd> <dd :class="{'cur' : sl.selectId == op.id }" v-for="(op,opi) in sl.options" :key="opi"> <a href="" @click.prevent="specSearch(sl ,op , )">{{op.option_name}}</a> </dd> </dl>
步骤四:选中规格,整体内容回显
specSearch (spec , option ) { //记录选中的id this.$set(spec,'selectId', option ? option.id : '' ) }
6.5前置技术:ElasticSearch
参考《elasticsearch入门.doc》6.6电商概念:SKU和SPU
SPU = Standard Product Unit (标准产品单位)
SPU是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。
通俗点讲,属性值、特性相同的商品就可以称为一个SPU。
例如:
iphone X就是一个SPU,与颜色、款式、套餐都无关。
SKU=stock keeping unit(库存量单位)
SKU即库存进出计量的单位, 可以是以件、盒、托盘等为单位。
SKU是物理上不可分割的最小存货单元。
例如:
iPhone X 128G 黑色,就是一个SKU总结
SPU:可以理解为一类商品的总称(一组产品特性的集合)
SKU:可以称之为最小库存单位,也就是所能唯一确定一件商品的唯一标识。6.7流程分析
sku查询功能:从数据库查询sku相关的所有信息
数据同步:将数据库中的数据,同步到es中。()
数据库查询搜索慢、es查询速度快
同步时机:1. 定时器(理论)、2. 测试程序(实施)
搜索服务:从elasticsearch中搜索数据,默认情况下es中没有数据,通过“数据同步”操作后,es有数据详细版流程
6.8表结构分析
商品相关表总览表间关系
6.9查询所有SKU
6.9.1接口
GET http://localhost:10010/web-service/sku/esData6.9.2后端实现:JavaBean
数据库相关JavaBean
Spu类似分类
Sku 商品详情
SkuComment sku的评论
ES相关JavaBean
ESData 准备存放到es中的数据,是以上三种数据的组合数据。SPU
package com.czxy.changgou4.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;import java.util.Date;
/**
-
Created by liangtong.
*/
@TableName(“tb_spu”)
@Data
public class Spu {@TableId(type = IdType.AUTO)
private Integer id;//spu名字
@TableField(value=“spu_name”)
private String spuName;
//spu副名称
@TableField(value=“spu_subname”)
private String spuSubname;
//商品logo
@TableField(value=“logo”)
private String logo;
//分类1Id
@TableField(value=“cat1_id”)
private Integer cat1Id;
//分类2ID
@TableField(value=“cat2_id”)
private Integer cat2Id;
//分类3Id
@TableField(value=“cat3_id”)
private Integer cat3Id;@TableField(value=“brand_id”)
private Integer brandId;
@TableField(exist = false)
private Brand brand;
//审核时间
@TableField(value=“check_time”)
private String checkTime;
//审核状态 审核状态,0:未审核,1:已通过,2:未通过
@TableField(value=“check_status”)
private String checkStatus;
//价格
@TableField(value=“price”)
private String price;
//是否上架
@TableField(value=“is_on_sale”)
private Integer isOnSale;
//上架时间
@TableField(value=“on_sale_time”)
private Date onSaleTime;
//删除时间
@TableField(value=“deleted_at”)
private String deletedAt;@TableField(value=“weight”)
private String weight;//商品描述
@TableField(value=“description”)
private String description;
//规格与包装
@TableField(value=“packages”)
private String packages;
//售后保障
@TableField(value=“aftersale”)
private String aftersale;
//规格列表,json串
@TableField(value=“spec_list”)
private String specList;@TableField(value=“created_at”)
private String createdAt;
@TableField(value=“updated_at”)
private String updatedAt;
}
Sku
package com.czxy.changgou4.pojo;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;/**
-
Created by liangtong.
*/
@TableName(“tb_sku”)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Sku {@TableId(type = IdType.AUTO)
private Integer id;
//库存量
@TableField(value=“stock”)
private Integer stock;@TableField(value=“spu_id”)
private Integer spuId;
@TableField(exist = false)
private Spu spu;
//sku名字
@TableField(value=“sku_name”)
private String skuName;@TableField(value=“images”)
private String images;
@TableField(value=“price”)
private Double price;//1:1|2:6|6:22
@TableField(value=“spec_info_id_list”)
private String specInfoIdList;
//规格列表码,格式:{“机身颜色”:“白色”,“内存”:“3GB”,“机身存储”:“16GB”}
@TableField(value=“spec_info_id_txt”)
private String specInfoIdTxt;
}
SkuComment
package com.czxy.changgou4.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.util.Date;
/**
-
Created by liangtong.
*/
@TableName(“tb_sku_comment”)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SkuComment {@TableId(type = IdType.AUTO)
private Integer id;
@TableField(value=“created_at”)
private Date createdAt;
@TableField(value=“updated_at”)
private Date updatedAt;@TableField(value=“user_id”)
private Integer userId;
@TableField(exist = false)
private User user;@TableField(value=“spu_id”)
private Integer spuId;
@TableField(exist = false)
private Spu spu;@TableField(value=“sku_id”)
private Integer skuId;
@TableField(exist = false)
private Sku sku;@TableField(value=“ratio”)
private String ratio;@TableField(value=“spec_list”)
private String specList;@TableField(value=“content”)
private String content;
@TableField(value=“star”)
private Integer star;
@TableField(value=“isshow”)
private String isShow;@TableField(value=“sn”)
private String sn;
}
SkuPhoto
package com.czxy.changgou4.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;/**
-
Created by liangtong.
*/
@TableName(“tb_sku_photo”)
@Data
public class SkuPhoto {@TableId(type = IdType.AUTO)
private Integer id;
//外键
@TableField(value=“sku_id”)
@JsonProperty(“sku_id”)
private Integer skuId;
@TableField(exist = false)
private Sku sku;@TableField(value=“url”)
private String url;
}
ESData
package com.czxy.changgou4.vo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.util.Date;
import java.util.Map;/**
-
Created by liangtong.
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ESData {private Integer id; // skuId
private String logo;//图片地址
private String skuName;//sku名字
private String all; // 所有需要被搜索的信息,包含标题,分类,甚至品牌
private Date onSaleTime;//上架时间
//品牌编号
private Integer brandId;
//分类id
private Integer catId;
//规格列表
private Map<String, Object> specs;// 可搜索的规格参数,key是参数名,值是参数值
private Double price;// 价格
private String spuName;
private Integer stock;
private String description;
private String packages;//规格与包装
private String aftersale;//售后保障
private String midlogo;
//评价数
private Integer commentCount;
// 销量
private Integer sellerCount;
}
6.9.3后端实现:搭建环境
步骤一:修改父项目的pom.xml,锁定fastjson的依赖<fastjson.version>1.2.9</fastjson.version>
com.alibaba fastjson ${fastjson.version}步骤二:修改web服务,添加fastjson依赖
com.alibaba fastjson步骤三:修改网关,确定 ESData路径
6.9.4后端实现:查询
SpuMapper:通过SpuId查询详情,含品牌信息
package com.czxy.changgou4.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.czxy.changgou4.pojo.Spu;
import org.apache.ibatis.annotations.*;/**
-
Created by liangtong.
*/
@Mapper
public interface SpuMapper extends BaseMapper {@Select(“select * from tb_spu where id = #{spuId}”)
@Results({
@Result(property = “spuName” , column = “spu_name”),
@Result(property = “spuSubname” , column = “spu_subname”),
@Result(property = “cat1Id” , column = “cat1_id”),
@Result(property = “cat2Id” , column = “cat2_id”),
@Result(property = “cat3Id” , column = “cat3_id”),
@Result(property = “brandId” , column = “brand_id”),
@Result(property = “checkTime” , column = “check_time”),
@Result(property = “checkStatus” , column = “check_status”),
@Result(property = “isOnSale” , column = “is_on_sale”),
@Result(property = “onSaleTime” , column = “on_sale_time”),
@Result(property = “deletedAt” , column = “deleted_at”),
@Result(property = “specList” , column = “spec_list”),
@Result(property = “onSaleTime” , column = “on_sale_time”),
@Result(property = “createdAt” , column = “created_at”),
@Result(property = “updatedAt” , column = “updated_at”),
@Result(property=“brand”, column=“brand_id”,
one=@One(
select=“com.czxy.changgou4.mapper.BrandMapper.selectById”
))
})
public Spu findSpuById(@Param(“spuId”) Integer spuId);
}
SkuCommentMapper:查询评论数
package com.czxy.changgou4.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.czxy.changgou4.pojo.SkuComment;
import org.apache.ibatis.annotations.*;import java.util.List;
/**
-
Created by liangtong.
*/
@Mapper
public interface SkuCommentMapper extends BaseMapper {/**
- 查询评论数
- @param spu_id
- @return
/
@Select("select count() from tb_sku_comment where spu_id = #{spu_id}")
public Integer findNumBySpuId(@Param(“spu_id”) Integer spu_id);
}
SkuMapper:查询所有sku,含对应的spu信息
package com.czxy.changgou4.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.czxy.changgou4.pojo.Sku;
import org.apache.ibatis.annotations.*;import java.util.List;
/**
-
Created by liangtong.
*/
@Mapper
public interface SkuMapper extends BaseMapper {@Select(“select * from tb_sku”)
@Results(id=“skuResult” , value={
@Result(id=true,column=“id”,property=“id”),
@Result(column=“stock”,property=“stock”),
@Result(column=“spu_id”,property=“spuId”),
@Result(column=“sku_name”,property=“skuName”),
@Result(column=“spec_info_id_list”,property=“specInfoIdList”),
@Result(column=“spec_info_id_txt”,property=“specInfoIdTxt”),
@Result(column=“spu_id”,property=“spu”,
one=@One(
select=“com.czxy.changgou4.mapper.SpuMapper.findSpuById”
))
})
public List findAllSkus();
}
SkuPhotoMapper
package com.czxy.changgou4.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.czxy.changgou4.pojo.SkuPhoto;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;import java.util.List;
/**
- Created by liangtong.
*/
@Mapper
public interface SkuPhotoMapper extends BaseMapper {
}
package com.czxy.changgou4.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.czxy.changgou4.pojo.Sku;
import com.czxy.changgou4.vo.ESData;import java.util.List;
/**
-
@author 桐叔
-
@email liangtong@itcast.cn
*/
public interface SkuService extends IService {/**
*- @return
*/
public List findESData();
}
- @return
package com.czxy.changgou4.service.impl;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.czxy.changgou4.mapper.SkuCommentMapper;
import com.czxy.changgou4.mapper.SkuMapper;
import com.czxy.changgou4.pojo.Sku;
import com.czxy.changgou4.service.SkuService;
import com.czxy.changgou4.vo.ESData;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;/**
-
@author 桐叔
-
@email liangtong@itcast.cn
*/
@Service
@Transactional
public class SkuServiceImpl extends ServiceImpl<SkuMapper, Sku> implements SkuService {@Resource
private SkuCommentMapper skuCommentMapper;@Override
public List findESData() {
//1 查询所有详情sku
List skulist = baseMapper.findAllSkus();//2 将SKU 转换成 ESData List<ESData> esDataList = new ArrayList<>(); for (Sku sku:skulist){ ESData esData = new ESData(); // id esData.setId(sku.getId()); // 图片地址 esData.setLogo(sku.getSpu().getLogo()); // 商品名称 esData.setSkuName(sku.getSkuName()); // all “华为xx {"机身颜色":"白色","内存":"3GB","机身存储":"16GB"} 荣耀 ” esData.setAll(sku.getSkuName()+" " + sku.getSpecInfoIdTxt() + " " +sku.getSpu().getBrand().getBrandName()); // on_sale_time esData.setOnSaleTime(sku.getSpu().getOnSaleTime()); // brand_id esData.setBrandId(sku.getSpu().getBrandId()); // cat_id esData.setCatId(sku.getSpu().getCat3Id()); // Map<String, Object> specs;// 可搜索的规格参数,key是参数名,值是参数值 Map<String,Object> specs = JSON.parseObject(sku.getSpecInfoIdTxt(), Map.class);
// Map newSpecs = new HashMap();
// for(String key : specs.keySet()){
// newSpecs.put(“spec” + key , specs.get(key));
// }esData.setSpecs(specs); // price 价格 esData.setPrice(sku.getPrice()); // spu_name esData.setSpuName(sku.getSpu().getSpuName()); // stock 库存 esData.setStock(sku.getStock()); // description esData.setDescription(sku.getSpu().getDescription()); // packages;//规格与包装 esData.setPackages(sku.getSpu().getPackages()); // aftersale;//售后保障 esData.setAftersale(sku.getSpu().getAftersale()); // midlogo; esData.setMidlogo(sku.getSpu().getLogo()); // comment_count; 评价数 Integer comment_count = skuCommentMapper.findNumBySpuId(sku.getSpu().getId()); esData.setCommentCount(comment_count); //销售量 esData.setSellerCount(10); esDataList.add(esData); } return esDataList; }
}
package com.czxy.changgou4.controller;
import com.czxy.changgou4.service.SkuService;
import com.czxy.changgou4.vo.BaseResult;
import com.czxy.changgou4.vo.ESData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;
/**
-
@author 桐叔
-
@email liangtong@itcast.cn
*/
@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 liangtong@itcast.cn
*/
@Configuration
@EnableElasticsearchRepositories
public class RestClientConfig extends AbstractElasticsearchConfiguration {@Override
@Bean
public RestHighLevelClient elasticsearchClient() {final ClientConfiguration clientConfiguration = ClientConfiguration.builder() .connectedTo("localhost:9200") .build(); return RestClients.create(clientConfiguration).rest();
}
}
启动类
package com.czxy.changgou4;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;/**
- @author 桐叔
- @email liangtong@itcast.cn
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class CGSearchServiceApplication {
public static void main(String[] args) {
SpringApplication.run(CGSearchServiceApplication.class,args);
}
}
6.10.2远程调用:查询所有
步骤一:编写JavaBean,拷贝ESData,并添加elasticsearch相关注解
package com.czxy.changgou4.vo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;import java.util.Date;
import java.util.Map;/**
-
Created by liangtong.
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(indexName = “skus”, type = “docs” )
public class SearchSku {
@Id
private Long id; // skuId
@Field(type = FieldType.Text)
private String logo;//图片地址
@Field(type = FieldType.Text ,analyzer = “ik_max_word”)
private String skuName;//sku名字
@Field(type = FieldType.Text ,analyzer = “ik_max_word”)
private String all; // 所有需要被搜索的信息,包含标题,分类,甚至品牌
@Field(type = FieldType.Date)
private Date onSaleTime;//商家时间
//品牌编号
@Field(type = FieldType.Integer)
private Integer brandId;
// 分类id
@Field(type = FieldType.Integer)
private Integer catId;//规格列表
//@IndexDynamicSettings
private Map<String, Object> specs;// 可搜索的规格参数,key是参数名,值是参数值
@Field(type = FieldType.Double)
private Double price;// 价格
@Field(type = FieldType.Text)
private String spuName;
@Field(type = FieldType.Integer)
private Integer stock;
@Field(type = FieldType.Text)
private String description;
@Field(type = FieldType.Text)
private String packages;//规格与包装
@Field(type = FieldType.Text)
private String aftersale;//售后保障
@Field(type = FieldType.Text)
private String midlogo;
//评价数
@Field(type = FieldType.Integer)
private Integer commentCount;
// 销量
@Field(type = FieldType.Integer)
private Integer sellerCount;
}
步骤二:远程调用
package com.czxy.changgou4.feign;
import com.czxy.changgou4.vo.BaseResult;
import com.czxy.changgou4.vo.ESData;
import com.czxy.changgou4.vo.SearchSku;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;import java.util.List;
/**
-
Created by liangtong.
*/
@FeignClient(value=“web-service”,path = “/sku”)
public interface SkuFeign {@GetMapping(“/esData”)
public BaseResult<List> findESData();
}
步骤三:测试类
package com.czxy.changgou4;
import com.czxy.changgou4.feign.SkuFeign;
import com.czxy.changgou4.repository.SkuRepository;
import com.czxy.changgou4.vo.BaseResult;
import com.czxy.changgou4.vo.SearchSku;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;import javax.annotation.Resource;
import java.util.List;/**
-
@author 桐叔
-
@email liangtong@itcast.cn
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = CGSearchServiceApplication.class)
public class TestSkuClient {
@Resource
private SkuFeign skuFeign;@Test
public void findAllSkus() throws Exception {
BaseResult<List> baseResult = skuFeign.findESData();
List list = baseResult.getData();
System.out.println(“总条数:” + list.size());
for(SearchSku ss:list){
System.out.println(ss);
}
}
}
6.10.3整合ElasticSearch
步骤一:创建repository类package com.czxy.changgou4.repository;
import com.czxy.changgou4.vo.SearchSku;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;/**
- Created by liangtong.
*/
public interface SkuRepository extends ElasticsearchRepository<SearchSku, Long> {
}
步骤二:测试创建索引
@Resource
private ElasticsearchRestTemplate elasticsearchRestTemplate;
@Test
public void createIndex(){
// 创建索引
this.elasticsearchRestTemplate.createIndex(SearchSku.class);
// 配置映射
this.elasticsearchRestTemplate.putMapping(SearchSku.class);
}步骤三:测试装载数据
@Resource
private SkuRepository skuRepository;@Test
public void loadData() throws Exception {
BaseResult<List> baseResult = skuFeign.findESData();
List list = baseResult.getData();
for(SearchSku ss:list){
ss.setAll(ss.toString());
System.out.println(ss);
}
this.skuRepository.saveAll(list);
}6.11基本查询
6.11.1查询所有
package com.czxy.changgou4;import com.czxy.changgou4.repository.SkuRepository;
import com.czxy.changgou4.vo.SearchSku;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.test.context.junit4.SpringRunner;import javax.annotation.Resource;
import java.util.List;/**
-
@author 桐叔
-
@email liangtong@itcast.cn
*/
@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 liangtong@itcast.cn
*/
@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 liangtong@itcast.cn
*/
@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 liangtong@itcast.cn
*/
@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 liangtong@itcast.cn
*/
@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 liangtong@itcast.cn
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = CGSearchServiceApplication.class)
public class TestSkuRepository {
@Resource
private SkuRepository skuRepository;@Test
public void testFind() {
//1 简单条件
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();// 1.1 根据分类查询
// boolQueryBuilder.must(QueryBuilders.termQuery(“catId”,76));
// 1.2 根据品牌查询
// boolQueryBuilder.must(QueryBuilders.termQuery(“brandId”,15127));
// 1.3 根据规格进行查询 机身颜色=灰色
// boolQueryBuilder.must(QueryBuilders.termQuery( “specs.机身颜色.keyword”, “灰色”));
// 1.4 范围查询:
// boolQueryBuilder.must(QueryBuilders.rangeQuery(“price”).gte(100000).lt(140000));
// 1.5 关键字查询 boolQueryBuilder.must(QueryBuilders.matchQuery("skuName","华为")); //2 复合条件 NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); queryBuilder.withQuery(boolQueryBuilder); //3 查询 Page<SearchSku> pageInfo = this.skuRepository.search(queryBuilder.build()); // 总条数 System.out.println(pageInfo.getTotalElements()); // 查询结果 pageInfo.getContent().forEach(System.out::println); }
}
6.11.7分页
package com.czxy.changgou4;import com.czxy.changgou4.repository.SkuRepository;
import com.czxy.changgou4.vo.SearchSku;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.test.context.junit4.SpringRunner;import javax.annotation.Resource;
import java.util.List;/**
-
@author 桐叔
-
@email liangtong@itcast.cn
*/
@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 liangtong@itcast.cn
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = CGSearchServiceApplication.class)
public class TestSkuRepository {
@Resource
private SkuRepository skuRepository;@Test
public void testFind() {
//1 简单条件
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();// 1.1 根据分类查询
// boolQueryBuilder.must(QueryBuilders.termQuery(“catId”,76));
// 1.2 根据品牌查询
// boolQueryBuilder.must(QueryBuilders.termQuery(“brandId”,15127));
// 1.3 根据规格进行查询 机身颜色=灰色
// boolQueryBuilder.must(QueryBuilders.termQuery( “specs.机身颜色.keyword”, “灰色”));
// 1.4 范围查询:
// boolQueryBuilder.must(QueryBuilders.rangeQuery(“price”).gte(100000).lt(140000));
//2 复合条件 NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); queryBuilder.withQuery(boolQueryBuilder); //2.1 分页查询 // 第一页 : 从0开始
// queryBuilder.withPageable(PageRequest.of(0 ,6));
//2.2 排序 queryBuilder.withSort(SortBuilders.fieldSort("price").order(SortOrder.ASC)); //3 查询 Page<SearchSku> pageInfo = this.skuRepository.search(queryBuilder.build()); // 总条数 System.out.println(pageInfo.getTotalElements()); // 查询结果 pageInfo.getContent().forEach(System.out::println); }
}
6.12后端实现:商品搜索
6.12.1分析6.12.2接口
POST http://localhost:10010/search-service/sku/search
{
“catId”: 76,
“current”: 1,
“size”: 5,
“keyword”: “64GB”,
“brandId”: “8557”,
“specList”: {
“机身颜色”: “金色”
},
“minPrice”: “100000”,
“maxPrice”: “140000”,
“sortBy”:“xl|jg|pl|sj”,
“sortWay”:“asc|desc”}
sortBy: 综合(zh)、销量(xl )、价格(jg)、评论(pl)、时间(sj)
sortWay: 升序(asc)、降序(desc)6.12.3初始化数据
update tb_spu set logo = ‘https://img12.360buyimg.com/n1/jfs/t17050/106/1124838205/250590/7e63050a/5abb6be2N853106d9.jpg’
where id in (2,3,4,5,7,124);6.12.4封装对象
SearchVo,用于封装搜索条件,含分页数据,继承PageRequest
package com.czxy.changgou4.vo;import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;import java.util.Map;
/**
- 封装搜索条件,含分页数据
- Created by liangtong.
*/
@Data
public class SearchVo extends PageRequest {
private String keyword; // 关键字搜索,预留
private Integer catId; // 3 级类目
private Integer brandId; // 品牌
private Map<String,String> specList; // 规格选项列表
private Double minPrice; //最低价格
private Double maxPrice; //最高价格
}
ReturnSku ,用于封装sku的响应结果(可以理解SearchSku缩减版,为页面定制)
package com.czxy.changgou4.vo;
import lombok.Data;
/**
- Sku页面定制响应对象
- Created by liangtong.
*/
@Data
public class ReturnSku {
private Long id;
private String goodsName; //sku名称
private Double price; //价格
private String midlogo; //sku logo
private Integer commentCount; //sku的评论
}
6.12.5实现
步骤一:编写service接口,使用 SkuRepository 从 es中进行查询
编写二:编写service实现类
步骤三:编写controller,从es查询指定分类的sku信息步骤一:编写service接口,使用 SkuRepository 从 es中进行查询
package com.czxy.changgou4.service;import com.czxy.changgou4.vo.SearchVo;
import java.util.Map;
/**
- @author 桐叔
- @email liangtong@itcast.cn
/
public interface SkuSearchService {
/*- 根据条件查询sku,并将结果封装到Map中
- map.count , 总条数
- map.list , 总记录
- @param searchVo
- @return 自定义封装数据
*/
public Map search(SearchVo searchVo);
}
步骤二:编写service实现类
package com.czxy.changgou4.service.impl;
import com.czxy.changgou4.repository.SkuRepository;
import com.czxy.changgou4.service.SkuSearchService;
import com.czxy.changgou4.vo.ReturnSku;
import com.czxy.changgou4.vo.SearchSku;
import com.czxy.changgou4.vo.SearchVo;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/**
-
@author 桐叔
-
@email liangtong@itcast.cn
*/
@Service
public class SkuSearchServiceImpl implements SkuSearchService {
@Resource
private SkuRepository skuRepository;@Override
public Map search(SearchVo searchVo) {
//1 创建查询构建器
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();//1.1 分类查询(必须) boolQueryBuilder.must(QueryBuilders.termQuery("catId",searchVo.getCatId())); //1.2 关键字查询 if(StringUtils.isNotBlank(searchVo.getKeyword())){ boolQueryBuilder.must(QueryBuilders.matchQuery("skuName",searchVo.getKeyword())); } //1.3 品牌查询 if(searchVo.getBrandId()!=null){ boolQueryBuilder.must(QueryBuilders.termQuery("brandId",searchVo.getBrandId())); } //1.4 规格查询 Map<String, String> filter = searchVo.getSpecList(); if(filter!=null){ for (Map.Entry<String, String> entry : filter.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); boolQueryBuilder.must(QueryBuilders.termQuery( "specs." + key + ".keyword", value)); } } //1.5 价格区间,统一单位:前端元 * 100 --> 后端分 if(searchVo.getMinPrice()!=null ){ boolQueryBuilder.must(QueryBuilders.rangeQuery("price").gte(searchVo.getMinPrice() * 100)); } if(searchVo.getMaxPrice()!=null){ boolQueryBuilder.must(QueryBuilders.rangeQuery("price").lte(searchVo.getMaxPrice() * 100)); } //2 复杂查询 NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); queryBuilder.withQuery( boolQueryBuilder ); //2.1 排序 // sortBy: 综合(zh)、销量(xl )、价格(jg)、评价(pj)、时间(sj) // sortBy: 升序(asc)、降序(desc) if (searchVo.getSortBy()!=null){ if(searchVo.getSortBy().equals("xl")&&searchVo.getSortWay().equals("asc")){ //销量升序 queryBuilder.withSort(SortBuilders.fieldSort("sellerCount").order(SortOrder.ASC)); }else if(searchVo.getSortBy().equals("xl")&&searchVo.getSortWay().equals("desc")) { // 销量降序 queryBuilder.withSort(SortBuilders.fieldSort("sellerCount").order(SortOrder.DESC)); }else if(searchVo.getSortBy().equals("jg")&&searchVo.getSortWay().equals("asc")){ // 价格升序 queryBuilder.withSort(SortBuilders.fieldSort("price").order(SortOrder.ASC)); }else if(searchVo.getSortBy().equals("jg")&&searchVo.getSortWay().equals("desc")) { // 价格降序 queryBuilder.withSort(SortBuilders.fieldSort("price").order(SortOrder.DESC)); }else if(searchVo.getSortBy().equals("pl")&&searchVo.getSortWay().equals("asc")){ // 评论升序 queryBuilder.withSort(SortBuilders.fieldSort("commentCount").order(SortOrder.ASC)); }else if(searchVo.getSortBy().equals("pl")&&searchVo.getSortWay().equals("desc")) { // 评论降序 queryBuilder.withSort(SortBuilders.fieldSort("commentCount").order(SortOrder.DESC)); }else if(searchVo.getSortBy().equals("sj")&&searchVo.getSortWay().equals("asc")){ // 上架时间 queryBuilder.withSort(SortBuilders.fieldSort("onSaleTime").order(SortOrder.ASC)); }else if(searchVo.getSortBy().equals("sj")&&searchVo.getSortWay().equals("desc")) { // 上架时间 queryBuilder.withSort(SortBuilders.fieldSort("onSaleTime").order(SortOrder.DESC)); } } // 2.2 分页 queryBuilder.withPageable(PageRequest.of(searchVo.getPageNum() - 1 ,searchVo.getPageSize())); //3 查询,获取结果 // 3.1 查询 Page<SearchSku> pageInfo = this.skuRepository.search(queryBuilder.build()); // 2.2 总条数 long total = pageInfo.getTotalElements(); // 3.3 获取返回结果 ,组装返回数据 List<SearchSku> list = pageInfo.getContent(); // 返回的结果 List<ReturnSku> returnList = new ArrayList<>(); for(SearchSku sku:list){ ReturnSku rs = new ReturnSku(); rs.setId(sku.getId().intValue()); rs.setGoodsName(sku.getSkuName()); rs.setMidlogo(sku.getLogo()); rs.setCommentCount(sku.getCommentCount()); rs.setPrice(sku.getPrice()); returnList.add(rs); } // 3.4 封装 Map result = new HashMap(); result.put("count" , total); result.put("list" ,returnList); return result;
}
}
步骤三:编写controller,从es查询指定分类的sku信息
package com.czxy.changgou4.controller;
import com.czxy.changgou4.service.SkuSearchService;
import com.czxy.changgou4.vo.BaseResult;
import com.czxy.changgou4.vo.SearchVo;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;
import java.util.Map;/**
-
@author 桐叔
-
@email liangtong@itcast.cn
*/
@RestController
@RequestMapping(“/sku”)
public class SkuSearchController {
@Resource
private SkuSearchService skuSearchService;@PostMapping(“/search”)
public BaseResult findSkus(@RequestBody SearchVo searchVo){if(searchVo.getCatId()==null){ return BaseResult.error("分类不能为空"); } Map search = skuSearchService.search(searchVo); return BaseResult.ok("查询成功",search);
}
}
6.12.6测试
http://localhost:10010/search-service/swagger-ui.html
{
“catId”: “76”,
“pageNum”: 1,
“pageSize”: 3,
“skuName”: “手机”,
“brandId”: 8557,
“specList”: {“机身颜色”:“白色”,“内存”:“4GB”,“机身存储”:“64GB”},
“minPrice”: 80000,
“maxPrice”: 90000
}6.13前端实现:
6.13.1商品搜索
步骤一:修改apiclient.js,发送ajax//搜索
search : ( params ) => {
return axios.post(‘/search-service/sku/search’, params )
}步骤二:修改data区域,添加分页条件、查询结果searchResult
data() {
return {
searchMap: { //搜索条件对象
catId: ‘’, //分类id
brandId: ‘’, // 品牌id
pageNum: 1, //分页:第几页
pageSize: 10 , //分页:每页显示条数}, brandList : [], //所有品牌 specList : [], //所有规格 searchResult: {} , //搜索结果 }
},
步骤三:修改searchList函数
async searchList () { // ajax 查询 let { data } = await this.$request.search( this.searchMap ) // 处理结果 this.searchResult = data.data },
步骤四:页面加载成功,查询数据
步骤五:展示数据
<!-- 商品列表 start--> <div class="goodslist mt10"> <ul> <li v-for="(goods,index) in searchResult.list" :key="index"> <dl> <dt><a href=""><img :src="goods.midlogo" alt="" /></a></dt> <dd><a href="goods.html">{{goods.goodsName}}</a></dd> <dd><strong>¥{{goods.price | priceHandler}}</strong></dd> <dd><a href=""><em>已有{{goods.commentCount}}人评价</em></a></dd> </dl> </li> </ul> </div> <!-- 商品列表 end-->
步骤六:价格优化,将分转换成元,且保留2位小数
filters: {
priceHandler(value) {
if(value) {
return (Number(value) / 100).toFixed(2)
}
return value
},
},步骤七:商品名称优化
filters: {
substr(value, len, suffix) {
// 如果待处理字符串长度小于指定长度,则返回待处理字符串
// 大于执行长度,截取字符串,并追加后缀
return value.length < len ? value : value.substring(0,len) + suffix
},
},6.13.2商品搜索–规格
步骤一:修改searchMap,完善搜索条件步骤二:完善 specSearch 函数
specSearch (spec , option ) {
//记录选中的id
this.$set(spec,‘selectId’, option ? option.id : ‘’ )
//设置选中参数
this.searchMap.specList[spec.spec_name] = option.option_name
//查询
this.searchList()
}6.13.3商品搜索–排序
步骤一:修改data区域,添加排序条件步骤二:修改标签
-
排序:
步骤三:编写sortSearch函数
sortSearch(sortBy) { if(this.searchMap.sortBy == sortBy) { //点击第二次切换排序方式 this.searchMap.sortWay = this.searchMap.sortWay == 'asc' ? 'desc' : 'asc' } else { //第一次默认升序 this.searchMap.sortBy = sortBy this.searchMap.sortWay = 'asc' } this.searchList() }
6.13.4商品搜索–价格区间
价格:
-
<input type=“submit” value=“搜索” @click.prevent=“searchList” />检查:前端价格单位和后端价格单位,是否统一?
后端价格单位:分
前端价格单位:元6.13.5商品搜索–分页
步骤一:拷贝分页组件步骤二:导入分页组件
步骤三:使用分页组件
<!--
组件
total属性:总条数 (查询结果)
page_size:每页显示个数(查询条件)
@page_changed :每一页需要执行函数
参数pageNum:表示第几页(当前页)
–>
<pagination :total=“searchResult.count”
:page_size=“searchMap.pageSize”
@page_changed=“pageChanged”>
步骤四:编写处理函数pageChanged
pageChanged : function( pageNum ){ //根据第几页查询数据 this.searchMap.pageNum = pageNum; //查询 this.searchList(); },
6.13.6商品搜索–关键字搜索
分析步骤一:修改 ~/pages/store/index.js ,在state区域添加keyword关键字
步骤二:修改 ~components/HeaderSearch.vue,搜索框绑定变量keyword
data() {
return {
keyword: ‘’
}
},步骤三:给搜索按钮绑定事件,将搜索框绑定的数据同步到vuex
// 获得vuex中map方法
import {mapMutations} from ‘vuex’export default {
methods: {
…mapMutations([‘setData’]), // 获得vuex中setData方法
search() {
// 调用setData方法,将数据同步到vuex中keyword变量
this.setData({‘key’:‘keyword’,‘value’: this.keyword})
}
},
}<input type=“submit” class=“btn” @click.prevent=“search” value=“搜索” />
步骤四:修改 ~/pages/list/_cid ,监听vuex中keyword,如果数据发送改变进行关键字查询watch: {
‘$store.state.keyword’(newValue, oldValue) {
this.searchMap.keyword = newValue
this.searchList()
},
},6.13.7品牌条件优化
需求:品牌条件太多,提供控制按钮,显示或隐藏多余品牌。步骤一:提供变量 brandMore,用于控制多余部门品牌的显示与隐藏。
步骤二:提供计算属性,用于控制品牌数据多少的显示
computed: {
brandFilter() {
// 显示所有 或 显示前19条
return this.brandMore ? this.brandList : this.brandList.slice(0,19)
},
},步骤三:显示提示信息
<span style=“cursor: pointer;” @click=“changeBrandMore”>{{this.brandMore ? ‘收取’: ‘更多’}}
步骤四:编写changeBrandMore() 函数,用于切换brandMore
changeBrandMore() {
this.brandMore=!this.brandMore
}7.详情页模块
7.1构建详情页
步骤0:确定访问路径
http://localhost:3000?Goods?id=1步骤一:创建 ~pages/Goods.vue 页面
步骤二:复制 ~/static/goods.html 内容,导入第三方资源(css、js)
head: {
title: ‘列表页面’,
link: [
{rel:‘stylesheet’,href: ‘/style/goods.css’},
{rel:‘stylesheet’,href: ‘/style/common.css’},
{rel:‘stylesheet’,href: ‘/style/bottomnav.css’},
{rel:‘stylesheet’,href: ‘/style/jqzoom.css’},
],
script: [
{ type: ‘text/javascript’, src: ‘/js/header.js’ },
{ type: ‘text/javascript’, src: ‘/js/goods.js’ },
{ type: ‘text/javascript’, src: ‘/js/jqzoom-core.js’ },
]
},步骤三:导入公共资源
步骤四:添加原页面js特效
7.2详情
7.2.1分析7.2.2接口
GET http://localhost:10010/web-service/sku/goods/2600242
返回值
{
id:“商品ID,skuid”,
goods_name:“商品名称”,
price:“价格”,
on_sale_date:“上架时间”,
comment_count:“评论数量”,
comment_level:“评论级别(1-5)”,
cat1_info:{
id:“分类ID”,
cat_name:“分类名称”
},
cat2_info:{
id:“分类ID”,
cat_name:“分类名称”
},
cat3_info:{
id:“分类ID”,
cat_name:“分类名称”
},
logo:{
smlogo:“小LOGO(50x50)”,
biglogo:“大LOGO(350x350)”,
xbiglogo:“超大LOGO(800x800)”
},
photos:[
{
smimg:“商品图片(50x50)”,
bigimg:“商品图片(350x350)”,
xbigimg:“商品图片(800x800)”
},
…
],
description:“商品描述”,
aftersale:“售后”,
stock:“库存量”,
spec_list:[
{
id:“规格ID”,
spec_name:“规格名称”,
options:[
{
id:“选项ID”,
option_name:“选项名称”
}
…
]
}
…
],
spec_info:{
id_list:“规格ID:选项ID|规格ID:选项ID|…”,
id_txt:“规格名称:规格选项|规格名称:规格选项|…”
},
sku_list:[
{
skuid:“SKUID”,
id_list:“规格ID:选项ID|规格ID:选项ID|…”
},
…
]
}7.2.3初始化数据
insert into tb_sku_photo(sku_id,url) values(2600242,‘http://img12.360buyimg.com/n1/s450x450_jfs/t1/100605/24/7603/222062/5dfc6d30Ec375bf0a/e29b6690731acb24.jpg’);
insert into tb_sku_photo(sku_id,url) values(2600242,‘http://img12.360buyimg.com/n1/s450x450_jfs/t1/110371/2/1323/189888/5dfc6d30E073c3495/cb256ec2d3cf9ae2.jpg’);
insert into tb_sku_photo(sku_id,url) values(2600242,‘http://img12.360buyimg.com/n1/s450x450_jfs/t1/95005/38/7465/139593/5dfc6d2fEd2317126/63b5253237353618.jpg’);7.2.4后端实现:JavaBean
SkuPhoto : sku对应的所有图片
OneSkuResult:用于封装sku详情步骤一:创建SkuPhoto,根据tb_sku_photo表编写内容
package com.czxy.changgou4.pojo;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;/**
-
Created by liangtong.
*/
@TableName(“tb_sku_photo”)
@Data
public class SkuPhoto {@TableId(type = IdType.AUTO)
private Integer id;
//外键
@TableField(value=“sku_id”)
@JsonProperty(“sku_id”)
private Integer skuId;
@TableField(exist = false)
private Sku sku;@TableField(value=“url”)
private String url;
}
步骤二:创建OneSkuResult,根据接口返回结果编写内容
package com.czxy.changgou4.vo;import com.czxy.changgou4.pojo.Category;
import com.czxy.changgou4.pojo.Specification;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;import java.util.Date;
import java.util.List;
import java.util.Map;/**
-
@author 桐叔
-
@email liangtong@itcast.cn
*/
@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
}
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 liangtong@itcast.cn
*/
@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 页面,商品详情
- 商品编号: {{goodsInfo.skuid}}
- 定价:¥{{goodsInfo.price*1.2}}
- 本店价: ¥{{goodsInfo.price}} (降价通知)
- 上架时间:2012-09-12
- 商品评分: (已有{{goodsInfo.comment_count}}人评价)
-
-
{{spec.spec_name}}:
- {{option.option_name}}
编写specOptionSelect方法
methods: {
specOptionSelect(spec,option) {
// 拼接标记符,规格id:选项id
let flag = spec.id + ‘:’ + option.id
// 判断id_list中是否有‘标记符’,如果没有返回-1
return this.goodsInfo.spec_info.id_list.indexOf(flag) != -1
}
},步骤六:修复bug,图片大小的原因,导致“放大镜”中等图太大,遮盖小图
问题图示解决
7.3规格操作
点击“规格”时,切换SKU的id步骤一:修改 Goods.vue 页面,给每一个规格选项绑定点击事件
<a @click.prevent=“selectSpec(sl.id , op.id)” v-for=“(op,opi) in sl.options” :key=“opi”
步骤二:修改 Goods.vue 页面,完成 selectSpec 函数
selectSpec(specId, optionId) { // 1. spec标记, 规格id:选项id,例如,1:1 var flag = specId + ":" + optionId; // 2. 将当前记录规则替换了 1:2|2:6 --> 1:1|2:6 var re = new RegExp( specId + ":\\d+"); var newFlag = this.goodsInfo.spec_info.id_list.replace(re ,flag); // 3. 从sku列表中,获得skuId,跳转页面 for(var i = 0 ; i< this.goodsInfo.sku_list.length ; i ++ ){ var id_list = this.goodsInfo.sku_list[i].id_list; if(id_list == newFlag) { location.href = "goods?id=" + this.goodsInfo.sku_list[i].skuid; break; } } }
7.4评论
7.4.1接口
GET http://localhost:10010/web-service/comments/spu/2?current=1&size=2
{
“code”: 20000,
“message”: “查询成功”,
“data”: {
“impressions”: [
{
“id”: 1,
“title”: “口感不错”,
“count”: 15326,
“spu_id”: 2,
“sku_id”: 2600242
}
],
“ratio”: {
“common”: “33.33”,
“bad”: “33.33”,
“goods”: “33.33”
},
“comment_count”: 3,
“comments”: [
{
“id”: 3,
“userId”: 1,
“user”: {
“face”: “images/user3.jpg”,
},
“spuId”: 2,
“skuId”: 2600248,
“ratio”: “2”,
“content”: “差”,
}
]
},
“other”: {}
}7.4.2分析
7.4.3后端实现:JavaBean
步骤一:创建 Impression ,用于封装印象表中的数据
package com.czxy.changgou4.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;/** 印象
-
@author 桐叔
-
@email liangtong@itcast.cn
*/
@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 liangtong@itcast.cn
*/
@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 liangtong@itcast.cn
*/
@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 liangtong@itcast.cn
/
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 liangtong@itcast.cn
*/
@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 liangtong@itcast.cn
*/
@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 ,展示“好评度”
步骤五:修改 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>
-
买家印象:
- {{ci.title}} ({{ci.count}})
步骤六:修改 Goods.vue ,展示“评论”
步骤七:修改 Goods.vue ,展示“分页条”
import Pagination from ‘…/components/Pagination’
<pagination :total="comments.comment_count"
:page_size=“commentPageSize”
@page_changed=“pageChanged” >
8.购物车模块
购物车数据2种形态:
登录态:保存到服务器端的redis中
没登录:保存在浏览器端 localStorage 中8.1搭建购物车服务:8095
步骤一:创建changgou4-service-cart 项目步骤二:修改pom.xml文件,添加坐标
com.czxy.changgou changgou4-common-auth com.czxy.changgou changgou4-pojo org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-data-redis redis.clients jedis com.alibaba.nacos nacos-client<!-- nacos 服务发现 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!-- openfeign 远程调用 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!--swagger2--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> </dependency> <!--fastjson--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> </dependency>
步骤三:创建yml文件,
#端口号
server:
port: 8095
spring:
application:
name: cart-service
redis:
host: 127.0.0.1
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 #nacos服务地址
#自定义内容
sc:
jwt:
secret: sc@Login(Auth}*^31)&czxy% # 登录校验的密钥
pubKeyPath: D:/rsa/rsa.pub # 公钥地址
priKeyPath: D:/rsa/rsa.pri # 私钥地址
expire: 360 # 过期时间,单位分钟步骤四:拷贝JWT配合类 + Swagger + Redis
步骤五:启动类
package com.czxy.changgou4;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;/**
- @author 桐叔
- @email liangtong@itcast.cn
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class CGCartServiceApplication {
public static void main(String[] args) {
SpringApplication.run(CGCartServiceApplication.class, args);
}
}
8.2添加到购物车
8.2.1整体分析8.2.2接口
POST http://localhost:10010/cart-service/carts
{
“skuid”: 2600242,
“count”: 5,
“checked”: true
}8.2.3后端实现:JavaBean
购物车列表项对象:CartItem (某一件商品的购买情况:商品、购买数量 等)
package com.czxy.changgou4.cart;import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;/**
- @author 桐叔
- @email liangtong@itcast.cn
*/
@Data
public class CartItem {
private Integer skuid;
private Integer spuid;
@JsonProperty(“goods_name”)
private String goodsName;
private Double price;
private Integer count;//购买数量
private Boolean checked;
private String midlogo;
@JsonProperty(“spec_info”)
private String specInfo;
}
购物车对象:Cart
package com.czxy.changgou4.cart;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;/**
-
@author 桐叔
-
@email liangtong@itcast.cn
*/
@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 );
}
}
购物车专门定制的对象
CartCategorypackage com.czxy.changgou4.vo;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;import java.util.ArrayList;
import java.util.List;/**
-
@author 桐叔
-
@email liangtong@itcast.cn
*/
@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 liangtong@itcast.cn
*/
@Data
public class CartSpecificationOption {private Integer id;
@JsonProperty(“spec_id”)
private Integer specId; //外键,规格IDprivate 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 liangtong@itcast.cn
*/
@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 liangtong@itcast.cn
*/
@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
}
8.2.4后端实现
步骤一:创建CartVo,用于封装请求参数
package com.czxy.changgou4.vo;
import lombok.Data;
/**
-
@author 桐叔
-
@email liangtong@itcast.cn
*/
@Data
public class CartVo {private Integer skuid ; //“SKUID”,
private Integer count; //“购买数量”
private Boolean checked; //“购买数量”
}
步骤二:创建SkuClient,用于查询详情
package com.czxy.changgou4.feign;
import com.czxy.changgou4.vo.BaseResult;
import com.czxy.changgou4.vo.OneSkuResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;/**
-
@author 桐叔
-
@email liangtong@itcast.cn
*/
@FeignClient(value=“web-service”,path = “/sku”)
public interface SkuClient {@GetMapping(“/goods/{skuid}”)
public BaseResult findSkuById(@PathVariable(“skuid”) Integer skuid);
}
步骤三:创建CartService接口,用于完成添加业务逻辑
package com.czxy.changgou4.service;
import com.czxy.changgou4.pojo.User;
import com.czxy.changgou4.vo.CartVo;/**
-
@author 桐叔
-
@email liangtong@itcast.cn
*/
public interface CartService {/**
- 给指定用户添加商品
- @param user
- @param cartVo
*/
public void addCart(User user , CartVo cartVo);
}
步骤四:创建CartService实现类
package com.czxy.changgou4.service.impl;
import com.alibaba.fastjson.JSON;
import com.czxy.changgou4.cart.Cart;
import com.czxy.changgou4.cart.CartItem;
import com.czxy.changgou4.feign.SkuClient;
import com.czxy.changgou4.pojo.User;
import com.czxy.changgou4.service.CartService;
import com.czxy.changgou4.vo.BaseResult;
import com.czxy.changgou4.vo.CartOneSkuResult;
import com.czxy.changgou4.vo.CartVo;
import com.czxy.changgou4.vo.OneSkuResult;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;
/**
-
@author 桐叔
-
@email liangtong@itcast.cn
*/
@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 liangtong@itcast.cn
*/
@RestController
@RequestMapping(“/carts”)
public class CartController {@Resource
private CartService cartService;@Resource
private HttpServletRequest request;@Resource
private JwtProperties jwtProperties;@PostMapping
public BaseResult addCart(@RequestBody CartVo cartVo){//1 获得用户信息 // 1.1 获得token String token = request.getHeader("Authorization"); // 1.2 解析token User loginUser = null; try { loginUser = JwtUtils.getObjectFromToken(token, jwtProperties.getPublicKey(),User.class); } catch (Exception e) { return BaseResult.error("token失效或未登录"); } //2 添加操作 cartService.addCart( loginUser , cartVo ); //3 提示 return BaseResult.ok("添加成功");
}
}
步骤六:测试
8.2.5前端实现:购买数量
步骤一:修改Goods.vue ,为文本框添加键盘事件,用于校验输入的数据<input type=“text” name=“amount” v-model=“buyCount” @keyup.prevent=“updateCount($event)” value=“1” class=“amount”/>
步骤二:修改Goods.vue ,完成updateCount函数
updateCount : function(e){ // e.target.value 获得用户输入的数据 //使用正则处理数字 if( /^\d+$/.test(e.target.value) ){ //如果是数字,小于1,默认为1 if( e.target.value < 1) { this.buyCount = 1; } } else { //默认为1 this.buyCount = 1; } },
步骤三:检查+和-已完成功能
8.2.6前端实现:
步骤一:修改 api.js ,完成“添加到购物车”方法//添加到购物车
addToCart : ( params ) => {
return axios.post(“/cart-service/carts”, params )
},步骤二:修改Goods.vue,给“加入购物车”绑定点击事件 addToCartFn
<input type=“submit” value=“” class=“add_btn” @click.prevent=“addToCartFn” />
步骤三:修改Goods.vue,完成addToCartFn功能
未登录:保存到sessionStorage
登录:保存到redis
待完善功能:用户登录时,将sessionStorage保存的商品信息合并到redis中
async addToCartFn(){
//获得登录标识
let token = sessionStorage.getItem(“token”);
if(token != null){
//登录:发送ajax进行添加
let newGoods = {
skuid: this.KaTeX parse error: Expected 'EOF', got '}' at position 55: …yCount }̲; //登录状…request.addToCart(newGoods)
if(data.code == 20000){
//location.href = “flow1”
this.$router.push(‘flow1’)
} else {
alert(data.data.errmsg);
}
return;
}//未登录:在浏览器保存 //1 准备添加物品数据 var newGoods = { skuid: this.goodsInfo.skuid, goods_name:this.goodsInfo.goods_name, price:this.goodsInfo.price, count:this.buyCount, checked:true, midlogo:this.goodsInfo.logo.smlogo, spec_info: JSON.stringify(this.goodsInfo.spec_info) }; //2 维护数据:本地已经存储信息 和 新添加信息 合并 var cartStr = localStorage.getItem("cart"); var cart; if(cartStr == null) { // 2.1 第一次添加,直接已数组方式添加 cart = [newGoods]; } else { //判断是否存在(将字符串转换数组、依次遍历) cart = JSON.parse(cartStr); //是否为新物品,默认为新的 let isNew = true; cart.forEach( g => { //已有数据的id 和 新物品id 是否一样 if(g.skuid == newGoods.skuid){ //不是新物品 isNew = false; // 2.3 已有,重复,先获得对应,修改数量 g.count += parseInt(newGoods.count); } }); if(isNew == true){ // 2.2 已有,不重复,先获得数组,后追加 cart.push(newGoods); } } //3 存放到浏览器 var cartStr = JSON.stringify(cart); localStorage.setItem("cart" , cartStr ); //4 跳转页面 location.href = "flow1" }
步骤四:编写flow1页面
购物车8.3查看购物车
8.3.1分析
用户如果没有登录,购物车存放在浏览器端的localStorage处,且以数组的方式进行存储。
用户如果登录了,购物车存放在redis中,以Cart对象字符串方式存储。
问题:前后端数据不一致,无法使用一个也flow1.vue进行数据展示
解决方案:将后端cart数据进行简化,Cart对象–>Map(data)–>List(values)结论:前端提供给页面统一数组,页面进行数据展示即可。
8.3.2接口
GET http://localhost:10010/cart-service/carts{
“code”: 1,
“message”: “查询成功”,
“data”: {
“data”: {
“2600242”: {
“skuid”: 2600242,
“spuid”: 2,
“price”: 84900.0,
“count”: 17,
“checked”: true,
“midlogo”: null,
“goods_name”: “华为 G9 青春版 白色 移动联通电信4G手机 双卡双待”,
“spec_info”: “{“id_list”:“1:1|2:6|6:22”,“id_txt”:”{\“机身颜色\”:\“白色\”,\“内存\”:\“3GB\”,\“机身存储\”:\“16GB\”}“}”
}
},
“total”: 1443300.0
},
“other”: {}
}8.3.3后端实现
步骤一:修改CartService,添加 queryCartList 方法,从redis查询的购物车信息
步骤二:修改CartController,添加queryCartList 方法,仅返回购物车中的数据步骤一:修改CartService,添加 queryCartList 方法,
/**
*- @param user
- @return
*/
public Cart queryCartList(User user);
步骤二:修改CartServiceImpl,从redis查询的购物车信息
/**-
查询购物车
-
@param user
-
@return
*/
public Cart queryCartList(User user) {
String key = “cart” + user.getId();
// 获取hash操作对象
String cartString = this.stringRedisTemplate.opsForValue().get(key);// 2 获得购物车,如果没有创建一个
return JSON.parseObject(cartString, Cart.class);
}
步骤三:修改CartController,添加queryCartList 方法,仅返回购物车中的数据
/**
-
查询购物车
-
@return
*/
@GetMapping
public BaseResult queryCartList() {//1 获得用户信息
// 1.1 获得token
String token = request.getHeader(“Authorization”);
// 1.2 解析token
User loginUser = null;
try {
loginUser = JwtUtils.getObjectFromToken(token, jwtProperties.getPublicKey(),User.class);
} catch (Exception e) {
return BaseResult.error(“token失效或未登录”);
}Cart cart = this.cartService.queryCartList(loginUser);
return BaseResult.ok(“查询成功”, cart.getData().values());
}
8.3.4前端实现:显示页面
步骤一:创建 ~/pages/flow1.vue 组件,拷贝 ~/static/flow1.html内容步骤二:导入js和css
head: {
title: ‘首页’,
link: [
{rel:‘stylesheet’,href: ‘/style/cart.css’},
],
script: [
{ type: ‘text/javascript’, src: ‘/js/cart1.js’ },
]
},步骤三:添加公共组件
8.3.5前端实现:显示购物车信息
步骤一:修改api.js 查询购物车信息
步骤二:页面加载成功后,获得购物车信息(如果登录从后端获取,如果没有登录从浏览器端获得)
步骤三:遍历显示购物车信息,
步骤四:通过计算属性,计算总价格步骤一:修改apiclient.js 查询购物车信息
//查询购物车
getCart : () => {
return axios.get(“/cart-service/carts”)
},步骤二:页面加载成功后,获得购物车信息(如果登录从后端获取,如果没有登录从浏览器端获得)
data() {
return {
cart : [], //购物车对象
}
},
async mounted() {
//获得token信息,表示登录
this.token = sessionStorage.getItem(“token”)
if(this.token != null) {
//登陆:服务器获得数据
let { data } = await this.$request.getCart()
this.cart = data.data
} else {//未登录:从浏览器本地获得购物车
let cartStr = localStorage.getItem(“cart”);
if(cartStr != null) {
this.cart = JSON.parse(cartStr);
}
}
},步骤三:遍历显示购物车信息
<tbody> <!-- 购物车列表 start --> <tr v-for="(goods,k) in cart" :key="k"> <td class="col1"> <a href=""><img :src="goods.midlogo" alt="" /></a> <strong><a href="">{{goods.goods_name}}</a></strong> </td> <td class="col2"> <span style="display: block;" v-for="(value,key,index) in JSON.parse(JSON.parse(goods.spec_info).id_txt)" :key="index">{{key}}:{{value}}</span> </td> <td class="col3">¥<span>{{ (goods.price/100).toFixed(2) }}</span></td> <td class="col4"> <a href="javascript:;" class="reduce_num" @click.prevent="minus(goods)"></a> <input type="text" name="amount" v-model="goods.count" @keyup="updateCount(goods,$event)" value="1" class="amount"/> <a href="javascript:;" class="add_num" @click.prevent="plus(goods)"></a> </td> <td class="col5">¥<span>{{ (goods.price / 100 * goods.count).toFixed(2) }}</span></td> <td class="col6"><a href="" @click.prevent="del(k)">删除</a></td> </tr> <!-- 购物车列表 end --> </tbody>
步骤四:通过计算属性,计算总价格
computed : {
totalPrice : function(){ //计算总价格
//所有小计的和
let sum = 0 ;
this.cart.forEach( g => {
sum += (g.price * g.count);
});
return (sum/100).toFixed(2);
}
},8.4购物车操作:修改
8.4.1分析8.4.2接口
PUT http://localhost:10010/cart-service/carts8.4.3后端实现:更新
步骤一:修改service接口
/**- 更新操作:如果数据存在修改数据,如果数据不存在删除数据
- @param user
- @param cartVoList
*/
public void updateCart(User user, List cartVoList) ;
步骤二:修改service实现类
/**
-
更新操作:如果数据存在修改数据,如果数据不存在删除数据
-
@param user
-
@param cartVoList
*/
public void updateCart(User user, List cartVoList) {
//1 获得购物车
String key = “cart” + user.getId();
String cartStr = stringRedisTemplate.opsForValue().get(key);
// 处理是否有购物车
Cart cart = JSON.parseObject( cartStr , Cart.class);
if(cart == null) {
throw new RuntimeException(“购物车不存在”);
}//2 更新
//2.1 处理请求数据
Map<Integer,CartVo> requestMap = new HashMap<>();
for (CartVo cartVo : cartVoList) {
requestMap.put(cartVo.getSkuid(), cartVo);
}//2.2 处理更新和删除
Set keySet = new HashSet<>(cart.getData().keySet());
for (Integer skuid : keySet) {
CartVo cartRequest = requestMap.get(skuId);
if(cartRequest != null) {
// 更新
cart.updateCart(cartRequest.getSkuid() , cartRequest.getCount() ,cartRequest.getChecked());
} else {
// 删除
cart.deleteCart(skuId);
}
}//3 保存购物车
stringRedisTemplate.opsForValue().set( key , JSON.toJSONString(cart) );
}
步骤三:修改controller
@PutMapping
public BaseResult updateCart(@RequestBody List cartVoList) {//1 获得用户信息 // 1.1 获得token String token = request.getHeader("Authorization"); // 1.2 解析token User loginUser = null; try { loginUser = JwtUtils.getObjectFromToken(token, jwtProperties.getPublicKey(),User.class); } catch (Exception e) { return BaseResult.error("token失效或未登录"); } try { this.cartService.updateCart(loginUser ,cartVoList); return BaseResult.ok("成功"); } catch (Exception e) { return BaseResult.error("失败"); }
}
8.4.4前端实现:修改
步骤0:修改apiclient.js,添加 updateCart函数
步骤一:修改flow1.vue 给按钮和文本框添加事件
步骤二:编写修改对应的事件
步骤三:编写购物车cart的监听函数,只要数据发生改变立即保存
步骤四:删除之前绑定js步骤0:修改apiclient.js,添加 updateCart函数
updateCart : ( params ) => {
return axios.put(“/gccartservice/carts” ,params )
},步骤一:修改flow1.vue 给按钮和文本框添加事件
步骤二:编写修改对应的事件
methods: {
minus : function(goods){
if( goods.count > 1) {
goods.count --;
}
},
plus : function(goods){
//可以考虑库存
goods.count ++;
},
updateCount : function(goods,e){
console.info(e.target.value);
if( /^\d+$/.test( e.target.value) ){
goods.count = e.target.value;
} else {
goods.count = 1;
}
},步骤三:编写购物车cart的监听函数,只要数据发生改变立即保存
watch: {
//深度监听
cart : {
async handler(val, oldVal) {
//1 更新/删除,数据
if(this.token != null){
let { data } = await this.$request.updateCart( val )
if(data.code == 1){
//alert(data.message);
}
} else {
//未登录
localStorage.setItem(“cart” , JSON.stringify( val ))
}
},
immediate: false, //true代表如果在 watch 里声明了之后,就会立即先去执行里面的handler方法
deep: true //深度监听,常用于对象下面属性的改变
}},
步骤四:删除之前绑定js
8.4.5前端实现:全选
步骤一:修改表格
步骤二:修改样式
步骤三:添加全选方法
步骤四:监听购物车数据,修改全选状态步骤一:修改表格
步骤二:修改样式
.mycart .col0{width: 5%;}
.mycart .col1{width: 35%;}步骤三:添加全选方法
data() {
return {
cart : [], //购物车对象
allChecked : false, //全选状态
}
},methods: {
checkAll : function(){
//所有列表项的状态,与全选的状态,相反
this.cart.forEach( g => {
g.checked = !this.allChecked;
});
},
},步骤四:监听购物车数据,修改全选状态
watch: {
//深度监听
cart : {
async handler(val, oldVal) {
//1 更新/删除,数据
if(this.token != null){
let { data } = await this.$request.updateCart( val )
if(data.code == 1){
//alert(data.message);
}
} else {
//未登录
localStorage.setItem(“cart” , JSON.stringify( val ))
}
//2 处理全选
let checkCount = 0;
this.cart.forEach( g => {
if( g.checked ) {
checkCount ++;
}
});
//全选状态,选中个数 与 总个数 比较结果
this.allChecked = (checkCount == this.cart.length);},
immediate: false, //true代表如果在 watch 里声明了之后,就会立即先去执行里面的handler方法
deep: true //深度监听,常用于对象下面属性的改变
}},
8.4.6后端实现:删除数据
只需要修改数据即可,watch已经完成删除操作步骤一:修改html绑定删除事件
步骤二:编写删除函数
del (index){ if(window.confirm("您确定要删除吗?")){ this.cart.splice(index , 1) } },
8.5结算
8.5.1跳转页面
步骤一:给结算绑定事件submit : function(){ if(this.token != null){ //登录 //location.href = "flow2.html"; this.$router.push('flow2') } else { //确定登录成功后调整的页面 localStorage.setItem("returnURL","flow2"); //没有登录 //location.href = "login"; this.$router.push('login') } },
步骤二:编写flow2.vue 组件
步骤三:完善登录
let returnURL = localStorage.getItem("returnURL"); if(returnURL != null){ //删除跳转页面缓存 localStorage.removeItem("returnURL"); //需要跳转页面 location.href= returnURL; } else { //默认:跳转首页 location.href = "/"; }
9.订单模块
9.1构建订单服务:8100
步骤一:构建项目,changgou4-service-orders步骤二:修改pom.xml文件,添加依赖
com.czxy.changgou changgou4-common-db com.czxy.changgou changgou4-common-auth com.czxy.changgou changgou4-pojo<!--web起步依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- nacos 客户端 --> <dependency> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-client</artifactId> </dependency> <!-- nacos 服务发现 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!--redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> <!-- openfeign 远程调用 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!--swagger2--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> </dependency> <!--fastjson--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> </dependency> <!--微信支付--> <dependency> <groupId>com.github.wxpay</groupId> <artifactId>wxpay-sdk</artifactId> <version>0.0.3</version> </dependency>
步骤三:修改yml 文件
server:
port: 8100spring:
application:
name: order-service
datasource:
url: jdbc:mysql://127.0.0.1:3306/changgou_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password: 1234
driver-class-name: com.mysql.jdbc.Driver
druid:
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 1000
test-on-borrow: true
redis:
database: 0
host: 127.0.0.1
port: 6379
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 #nacos服务地址sc:
worker:
workerId: 1
datacenterId: 1
jwt:
secret: sc@Login(Auth}*^31)&czxy% # 登录校验的密钥
pubKeyPath: D:/rsa/rsa.pub # 公钥地址
priKeyPath: D:/rsa/rsa.pri # 私钥地址
expire: 360 # 过期时间,单位分钟
pay:
appID: wx8397f8696b538317
mchID: 1473426802
key: T6m9iK73b0kn9g5v426MKfHQH7X8rKwb
httpConnectTimeoutMs: 5000
httpReadTimeoutMs: 10000步骤四:启动类
package com.czxy.changgou4;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class CGOrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(CGOrderServiceApplication.class, args);
}
}9.2收货人列表
9.2.1接口
GET http://localhost:10010/order-service/address
{
“code”: 20000,
“message”: “查询成功”,
“data”: [
{
“id”: 1,
“userId”: 1,
“shr_name”: “小明”,
“shr_mobile”: “13344445555”,
“shr_province”: “江苏省”,
“shr_city”: “宿迁市”,
“shr_area”: “沭阳县”,
“shr_address”: “常州路57号”,
“isdefault”: 0
}
],
“other”: {}
}9.2.2后端实现:JavaBean
步骤一:创建Address地址对象步骤二:根据表结构和接口规范,编写内容
package com.czxy.changgou4.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;/**
-
Created by liangtong.
*/
@TableName(“tb_address”)
@Data
public class Address {@TableId(type = IdType.AUTO)
private Integer id;
//用户ID
@TableField(value = “user_id”)
private Long userId;
//收货人姓名
@TableField(value = “shr_name”)
@JsonProperty(“shr_name”)
private String shrName;
//收货人手机
@TableField(value = “shr_mobile”)
@JsonProperty(“shr_mobile”)
private String shrMobile;
//收货人省份
@TableField(value = “shr_province”)
@JsonProperty(“shr_province”)
private String shrProvince;
//收货人城市
@TableField(value = “shr_city”)
@JsonProperty(“shr_city”)
private String shrCity;
//收货人地区
@TableField(value = “shr_area”)
@JsonProperty(“shr_area”)
private String shrArea;
//收货人详情地址
@TableField(value = “shr_address”)
@JsonProperty(“shr_address”)
private String shrAddress;
//1:默认;0:不是
@TableField(value = “isdefault”)
@JsonProperty(“isdefault”)
private Integer isdefault;
}
9.2.3后端实现:
需求:查询当前登录用户收货人列表
需要通过token获得用户信息步骤一:拷贝配置类
步骤二:编写mapper,使用通用mapper即可
package com.czxy.changgou4.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.czxy.changgou4.pojo.Address;
import org.apache.ibatis.annotations.Mapper;@Mapper
{
public interface AddressMapper extends BaseMapper
}步骤三:编写service接口,编写findAllByUserId 方法完成功能
package com.czxy.changgou4.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.czxy.changgou4.pojo.Address;import java.util.List;
public interface AddressService extends IService
{/** * 查询指定用户的所有地址 * @param userId * @return */ public List<Address> findAllByUserId(Long userId) ;
}
步骤四:编写service实现
步骤五:编写controller
package com.czxy.changgou4.controller;
import com.czxy.changgou4.config.JwtProperties;
import com.czxy.changgou4.pojo.Address;
import com.czxy.changgou4.pojo.User;
import com.czxy.changgou4.service.AddressService;
import com.czxy.changgou4.utils.JwtUtils;
import com.czxy.changgou4.vo.BaseResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List;@RestController
@RequestMapping(“/address”)
public class AddressController {@Resource private AddressService addressService; @Resource private JwtProperties jwtProperties; @Resource private HttpServletRequest request; @GetMapping public BaseResult queryAddress(){ //1 获得用户信息 // 1.1 获得token String token = request.getHeader("Authorization"); // 1.2 解析token User loginUser = null; try { loginUser = JwtUtils.getObjectFromToken(token, jwtProperties.getPublicKey(),User.class); } catch (Exception e) { return BaseResult.error("token失效或未登录"); } //2 查询 List<Address> list = this.addressService.findAllByUserId( loginUser.getId() ); return BaseResult.ok("查询成功", list ); }
}
9.2.4前端实现
需求:查询所有的收货人地址
为了操作方便,需要使用filter过滤默认地址步骤一:修改 apiclient.js ,添加查询收货人列表函数
getAddress : () => {
return axios.get(“/order-service/address”)
},步骤二:修改 flow2.vue 组件,页面加载成功,查询当前登录用户所有的收货人地址
同时过滤默认地址数据mounted() {
// 查询收获人地址
this.getAddressFn()
},
data() {
return {
addressList: [], //所有的地址
defaultAddress: {}, //默认地址
}
},
methods: {
async getAddressFn() {
let { data } = await this.$request.getAddress()
// 所有收获人地址
this.addressList = data.data
//默认地址
this.defaultAddress = this.addressList.filter(item => item.isdefault == 1)[0];
}
},步骤三:修改 flow2.vue 组件,显示默认地址
{{defaultAddress.shr_name}} {{defaultAddress.shr_mobile}}
{{defaultAddress.shr_province}} {{defaultAddress.shr_city}} {{defaultAddress.shr_area}} {{defaultAddress.shr_address}}
步骤四:修改flow2.vue,显示收货人地址列表
{{addr.shr_name}} {{addr.shr_province}} {{addr.shr_city}} {{addr.shr_area}} {{addr.shr_address}} {{addr.shr_mobile}}
设为默认地址
编辑
删除
-
9.3添加联系人
9.3.1需求
需求:新添加的联系人为默认联系人9.3.2显示添加表单
修改flow2.vue,显示添加表单data() {
return {
addressList: [], //所有的地址
defaultAddress: {}, //默认地址
showNew: false, //是否显示新地址
}
},使用变量,控制添加表单的显示与隐藏
<input type=“radio” name=“address” checked=“addr.isdefault == 1” @click=“clickRadio(addr)” />
{{addr.shr_name}} {{addr.shr_province}} {{addr.shr_city}} {{addr.shr_area}} {{addr.shr_address}} {{addr.shr_mobile}}
设为默认地址
编辑
删除{{showNew==false && addr.isdefault == 1}}
- <input type=“radio” name=“address” class=“new_address” @click=“showNew=true” />使用新地址
9.3.3接口
POST http://localhost:10010/order-service/address
{
“shr_name”: “张三”,
“shr_mobile”: “13344445555”,
“shr_province”: “江苏省”,
“shr_city”: “宿迁市”,
“shr_area”: “沭阳县”,
“shr_address”: “常州路57号”
}9.3.4后端实现
步骤一:修改mapper,添加指定用户的地址默认状态package com.czxy.changgou4.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.czxy.changgou4.pojo.Address;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;@Mapper
{
public interface AddressMapper extends BaseMapper/** * 修改用户默认地址 * @param userId * @param defaultValue */ @Update("update tb_address set isdefault = #{defaultValue} where user_id = #{userId}") public void updateDefault(@Param("userId") Integer userId, @Param("defaultValue") int defaultValue);
}
步骤二:修改service,添加地址,并将新地址设置成默认地址。
/**
- 添加新地址,并设置成默认地址
- @param address
*/
public void addAddress(Address address) ;
步骤三:修改service实现
/**
-
添加新地址,并设置成默认地址
-
@param address
*/
public void addAddress(Address address) {
//修改指定用户的状态
this.addressMapper.updateDefault(address.getUserId(),0);//添加新地址,默认
address.setIsdefault(1);
this.addressMapper.insert(address);
}
步骤四:修改controller
@PostMapping
public BaseResult addAddress(@RequestBody Address address){
//1 获得用户信息
// 1.1 获得token
String token = request.getHeader(“Authorization”);
// 1.2 解析token
User loginUser = null;
try {
loginUser = JwtUtils.getObjectFromToken(token, jwtProperties.getPublicKey(),User.class);
} catch (Exception e) {
return BaseResult.error(“token失效或未登录”);
}address.setUserId(loginUser.getId().intValue()); this.addressService.addAddress(address); return BaseResult.ok("添加成功");
}
9.3.5前端实现
步骤一:修改apiclient.js,添加函数addNewAddress : ( params ) => {
return axios.post(“/order-service/address” , params )
},步骤二:修改 flow2.vue ,添加成员变量
data() {
return {
addressList: [], //所有的地址
defaultAddress: {}, //默认地址
showNew : false, //是否显示添加
newAddress : { //新地址,需要与表单进行数据绑定
shr_name:“”,
shr_mobile:“”,
shr_province:“”,
shr_city:“”,
shr_area:“”,
shr_address:“”
},
}
},步骤三:表单元素绑定
步骤四:绑定提交事件
methods: {
async getAddressFn() {
let { data } = await this.KaTeX parse error: Expected 'EOF', got '}' at position 176: … == 1)[0]; }̲, async add…request.addNewAddress(this.newAddress)
if(data.code == 1){
// 重新查询
this.getAddressFn()
// 表单隐藏,数据清空
this.showNew = false;
this.newAddress = {
shr_name:“”,
shr_mobile:“”,
shr_province:“”,
shr_city:“”,
shr_area:“”,
shr_address:“”
}
}
},
},9.4显示勾选商品
步骤一:页面加载成功,查询已经勾选商品
async mounted() {
// 查询收获人地址
this.getAddressFn()//2 查询需要购买的物品 let { data : cart } = await this.$request.getCart() this.cart = cart.data.filter( g => { //return true 数据返回,return false 数据被忽略 return g.checked; });
},
步骤二:展示商品基本信息
{{goods.goods_name}}
{{key}}:{{value}}
¥{{ (goods.price/100).toFixed(2) }}
{{goods.count}}
¥{{ (goods.price / 100 * goods.count).toFixed(2) }}
步骤三:展示商品概述信息
步骤四:使用计算属性显示总价格
computed: {
totalPrice : function(){ //计算总价格
//所有小计的和
let sum = 0 ;
this.cart.forEach( g => {
sum += (g.price * g.count);
});
return (sum/100).toFixed(2);
}
},9.5添加订单
9.5.1接口:下订单
POST http://localhost:10010/order-service/orders
{
“address_id”: 1
}9.5.2接口:更新库存
PUT http://localhost:10010/web-service/sku/goods/2600242?count=19.5.3下订单分析
9.5.4后端实现:JavaBean
OrderVo:用于封装请求数据
OrderGoods:订单详情封装对象
Order:订单表封装对象OrderVo:用于封装请求数据
package com.czxy.changgou4.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;import java.util.Map;
/**
*
*/
@Data
public class OrderVo {//收货人地址ID @JsonProperty("address_id") private Integer addressId; //送货方式 @JsonProperty("post_method") private Integer postMethod; //支付方式 @JsonProperty("pay_method") private Integer payMethod; //发票 private Map<Object,Object> invoice;
}
OrderGoods:订单详情封装对象
package com.czxy.changgou4.pojo;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.core.annotation.Order;import java.io.Serializable;
/**
*
*/
@TableName(“tb_order_good”)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OrderGoods implements Serializable {
@TableId(type = IdType.AUTO)
private Integer id;@TableField(value ="sn") private Long sn; @TableField(exist = false) private Order order; @TableField(value ="sku_id") private Integer skuId; @TableField(exist = false) private Sku sku; @TableField(value ="spu_id") private Integer spuId; //购买数量 @TableField(value ="number") private Integer number; //规格列表 @TableField(value ="spec_list") private String specList; //商品名称 @TableField(value ="sku_name") private String skuName; @TableField(value ="url") private String logo; //价格 @TableField(value ="price") private Double price;
}
Order:订单表封装对象
package com.czxy.changgou4.pojo;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.io.Serializable;
import java.util.Date;/**
-
@author 桐叔
-
@email liangtong@itcast.cn
*/
@TableName(“tb_order”)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order implements Serializable {//订单序列号
@TableField(value =“sn”)
//转换JSON时,将Long转换String,解决精度丢失问题
@JsonSerialize(using= ToStringSerializer.class)
private Long sn;
@TableField(value =“created_at”)
private Date createdAt;
@TableField(value =“updated_at”)
private Date updatedAt;//收货人姓名
@TableField(value =“shr_name”)
private String shrName;
//收货人手机
@TableField(value =“shr_mobile”)
private String shrMobile;
//收货人省份
@TableField(value =“shr_province”)
private String shrProvince;
//收货人城市
@TableField(value =“shr_city”)
private String shrCity;
//收货人地区
@TableField(value =“shr_area”)
private String shrArea;
//收货人详情地址
@TableField(value =“shr_address”)
private String shrAddress;//订单状态,0:未支付、1:已支付、等待发货、2:已发货、等待收货 3:已收货、等待评论 4:已结束 5:申请售后
@TableField(value =“status”)
private Integer status;//支付时间
@TableField(value =“pay_time”)
private String payTime;
//发货时间
@TableField(value =“post_time”)
private String postTime;
//用户ID
@TableField(value =“user_id”)
private Long userId;
@TableField(exist = false)
private User user;
//订单总价
@TableField(value =“total_price”)
private Double totalPrice;
}
9.5.5后端实现:更新库存
需求:在web服务中,更新sku的库存步骤一:修改service,完成更新操作
/**
- 更新
- @param skuid
- @param count
*/
public void updateSkuNum(Integer skuid, Integer count);
步骤二:编写service实现类
@Override
public void updateSkuNum(Integer skuid, Integer count) {
// 查询
Sku sku = baseMapper.selectById(skuid);
// 修改数据
sku.setStock( sku.getStock() - count);
// 更新
baseMapper.updateById(sku);
}步骤三:编写controller
/**
- 更新
- @param skuid
- @param count
- @return
*/
@PutMapping(“/goods/{skuid}”)
public BaseResult updateSkuNum(@PathVariable(“skuid”) Integer skuid , @RequestParam(“count”) Integer count){
skuService.updateSkuNum(skuid , count);
return BaseResult.ok(“更新成功”);
}
9.5.6后端实现:下订单
步骤一:雪花算法工具类
package com.czxy.changgou4.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;@Data
@ConfigurationProperties(prefix = “sc.worker”)
public class IdWorkerProperties {private long workerId;// 当前机器id private long datacenterId;// 序列号
}
package com.czxy.changgou4.config;
import com.czxy.changgou4.utils.IdWorker;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
@EnableConfigurationProperties(IdWorkerProperties.class)
public class IdWorkerConfig {@Bean public IdWorker idWorker(IdWorkerProperties prop) { return new IdWorker(prop.getWorkerId(), prop.getDatacenterId()); }
}
步骤二:编写SkuFeign,远程更新库存数量
package com.czxy.changgou4.feign;
import com.czxy.changgou4.vo.BaseResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestParam;@FeignClient(value = “web-service”,path = “/sku”)
public interface SkuFeign {@PutMapping("/goods/{skuid}") public BaseResult updateSkuNum(@PathVariable("skuid") Integer skuid , @RequestParam("count") Integer count);
}
步骤三:编写mapper
package com.czxy.changgou4.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.czxy.changgou4.pojo.OrderGoods;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface OrderGoodsMapper extends BaseMapper {
}package com.czxy.changgou4.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.czxy.changgou4.pojo.Order;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface OrderMapper extends BaseMapper {
}步骤四:编写service接口
package com.czxy.changgou4.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.czxy.changgou4.pojo.Order;
import com.czxy.changgou4.pojo.User;
import com.czxy.changgou4.vo.CartVo;
import com.czxy.changgou4.vo.OrderVo;import java.util.List;
public interface OrderService extends IService {
/**
*
* @param user
* @param orderVo
* @return
*/
public Long createOrder(User user , OrderVo orderVo);}
步骤五:编写service实现类
package com.czxy.changgou4.service.impl;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.czxy.changgou4.cart.Cart;
import com.czxy.changgou4.cart.CartItem;
import com.czxy.changgou4.feign.SkuFeign;
import com.czxy.changgou4.mapper.AddressMapper;
import com.czxy.changgou4.mapper.OrderGoodsMapper;
import com.czxy.changgou4.mapper.OrderMapper;
import com.czxy.changgou4.pojo.Address;
import com.czxy.changgou4.pojo.Order;
import com.czxy.changgou4.pojo.OrderGoods;
import com.czxy.changgou4.pojo.User;
import com.czxy.changgou4.service.OrderService;
import com.czxy.changgou4.utils.IdWorker;
import com.czxy.changgou4.vo.CartVo;
import com.czxy.changgou4.vo.OrderVo;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;
import java.util.*;@Service
@Transactional
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {
@Resource
private IdWorker idWorker;@Resource private AddressMapper addressMapper; @Resource private StringRedisTemplate stringRedisTemplate; @Resource private OrderMapper orderMapper; @Resource private OrderGoodsMapper orderGoodsMapper; @Resource private SkuFeign skuFeign; public Long createOrder(User user , OrderVo orderVo) { //1 生成订单 Order order = new Order(); //1.1 设置订单号 // 生成orderId long sn = idWorker.nextId(); order.setSn(sn); //1.2 设置用户信息 order.setUserId(user.getId()); //1.3 设置地址信息 // 获得前台传输过来的收货地址和收货人信息,生成订单,保存到数据库 Address address = addressMapper.selectById(orderVo.getAddressId()); order.setShrName(address.getShrName()); order.setShrMobile(address.getShrMobile()); order.setShrProvince(address.getShrProvince()); order.setShrCity(address.getShrCity()); order.setShrArea(address.getShrArea()); order.setShrAddress(address.getShrAddress()); //1.4 设置状态:创建订单的时候,默认情况是未支付状态 order.setStatus(0); order.setCreatedAt(new Date()); //2 获得购物车:从redis获得当前用户对应的购物车 String key = "cart" + user.getId(); String cartJsonStr = stringRedisTemplate.opsForValue().get(key); Cart cart = JSON.parseObject(cartJsonStr, Cart.class); //1.5 设置总价格 order.setTotalPrice(cart.getTotal()); //1.6 保存订单 orderMapper.insert(order); //3 保存购物车中已经勾选的商品信息 // 3.1 遍历购物项 Iterator<CartItem> it = cart.getData().values().iterator(); while (it.hasNext()) { CartItem cartItem = it.next(); if (cartItem.getChecked()) { // 3.2 将购物车中商品的信息赋值给OrderGoods OrderGoods orderGoods = new OrderGoods(); orderGoods.setSn(idWorker.nextId()); orderGoods.setSkuId(cartItem.getSkuid()); orderGoods.setSpuId(cartItem.getSpuid()); orderGoods.setNumber(cartItem.getCount()); orderGoods.setSpecList(cartItem.getSpecInfo()); orderGoods.setSkuName(cartItem.getGoodsName()); orderGoods.setLogo(cartItem.getMidlogo()); orderGoods.setPrice(cartItem.getPrice()); // 3.3 保存购物车中的商品信息 orderGoodsMapper.insert(orderGoods); // 3.4 购物车中移除该商品 it.remove(); // 3.5 远程调用方法,将该商品的数量减少 skuFeign.updateSkuNum(cartItem.getSkuid(), cartItem.getCount()); } } //3.6 更新redis购物车 stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(cart)); //4 返回sn return sn; }
}
步骤五:编写controller
package com.czxy.changgou4.controller;
import com.czxy.changgou4.config.JwtProperties;
import com.czxy.changgou4.pojo.User;
import com.czxy.changgou4.service.OrderService;
import com.czxy.changgou4.utils.JwtUtils;
import com.czxy.changgou4.vo.BaseResult;
import com.czxy.changgou4.vo.OrderVo;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;@RestController
@RequestMapping(“/orders”)
public class OrderController {@Resource private OrderService orderService; @Resource private HttpServletRequest request; @Resource private JwtProperties jwtProperties; @PostMapping public BaseResult createOrder(@RequestBody OrderVo orderVo) { //1 获得用户信息 // 1.1 获得token String token = request.getHeader("Authorization"); // 1.2 解析token User loginUser = null; try { loginUser = JwtUtils.getObjectFromToken(token, jwtProperties.getPublicKey(),User.class); } catch (Exception e) { return BaseResult.error("token失效或未登录"); } Long sn = this.orderService.createOrder(loginUser , orderVo); //将Long转换成字符串,否则丢失数据精度 return BaseResult.ok("订单创建成功").append("sn", sn + ""); }
}
9.5.7前端实现
步骤一:修改api.js,编写添加订单函数addOrder : ( orderVo ) => {
return axios.post(“/order-service/orders”, orderVo )
},步骤二:给“提交订单”按钮绑定点击事件
<a href=“” @click.prevent=“addOrderFn”>提交订单
步骤三:添加订单操作
async addOrderFn (){
//准备数据
{{cc.created_at}}
{{cc.content}}
回复(0)
有用(0)