项目笔记
Elastic Search
安装与使用
整合到Spring Boot
- 新建微服务wlmall-search
- 导入ElasticSearch依赖
<properties>
//因为Spring Boot会根据其版本来自动更换elasticsearch的依赖,所以在这指定版本
<elasticsearch.version>7.4.2</elasticsearch.version>
</properties>
//注意版本对应
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.4.2</version>
</dependency>
- 添加配置类
@SpringBootConfiguration
public class ElasticSearchConfig {
private static final RequestOptions COMMON_OPTIONS;
static {
RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
COMMON_OPTIONS = builder.build();
}
@Bean
public RestHighLevelClient esRestClient(){
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("Ip",9200,"http")
)
);
return client;
}
}
- 将该微服务加入到注册中心去。
- 插入数据的使用
IndexRequest indexRequest = new IndexRequest("索引名");
indexRequest.id("指定插入数据的id,不指定会默认生成");
//想要插入的数据,封装成对象,放到这里,转化成Json
String s = JSONValue.toJSONString(对象);
indexRequest.source(s, XContentType.JSON);
IndexResponse index = null;
try {
//ElasticSearchConfig.COMMON_OPTIONS是上面的配置类定义的
index = client.index(indexRequest, ElasticSearchConfig.COMMON_OPTIONS);
} catch (IOException e) {
e.printStackTrace();
}
//输出插入的结果
System.out.println(index);
- 查询的使用
SearchRequest searchRequest = new SearchRequest();
searchRequest.indices("索引名");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
//sourceBuilder.query(查询条件); 如下是查询所有address等于mill的
sourceBuilder.query(QueryBuilders.matchQuery("address","mill"));
searchRequest.source(sourceBuilder);
SearchResponse searchResponse = null;
try {
searchResponse = client.search(searchRequest, ElasticSearchConfig.COMMON_OPTIONS);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(searchResponse.toString());
注:具体使用可参考官方文档
商城业务
商品的上架
大致代码和顺序
Controller
@PostMapping("/{spuId}/up")
public R spuUp(@PathVariable("spuId") Long spuId){
spuInfoService.up(spuId);
return R.ok();
}
Service
public void up(Long spuId) {
//根据spuId来获取SkuInfo的实体对象
List<SkuInfoEntity> skuInfoEntities = skuInfoService.getSkusBySpuId(spuId);
//根据SpuId获取所有的规格属性
List<ProductAttrValueEntity> baseAttrs = productAttrValueService.baseAttrlistforspu(spuId);
//收集所有的规格属性的attrId
List<Long> attrIds = baseAttrs.stream().map(ProductAttrValueEntity::getAttrId).collect(Collectors.toList());
//根据attrIds获取所有可以当作检索条件的attr
List<Long> searchAttrIds = attrService.selectSearchAttrIds(attrIds);
//将查到的Id都存入Set集合中去
Set<Long> idSet = new HashSet<>(searchAttrIds);
//先过滤掉不能被检索的属性,再将过滤后的一些属性赋值给要上架的对象实体并收集起来
List<SkuEsModel.Attrs> attrsList = baseAttrs.stream()
.filter(item -> idSet.contains(item.getAttrId()))
.map(item -> {
SkuEsModel.Attrs attrs = new SkuEsModel.Attrs();
BeanUtils.copyProperties(item, attrs);
return attrs;
}).collect(Collectors.toList());
//收集所有的SkuId
List<Long> skuIdList = skuInfoEntities.stream().map(SkuInfoEntity::getSkuId).collect(Collectors.toList());
//远程调用来判断是否还有库存, 使用try,catch是为了使远程调用失败的时候将库存的值设为null
Map<Long, Boolean> stockMap = null;
try {
R skuHasStock = wareFeignService.getSkusHasStock(skuIdList);
//将调用返回来的数据转化为List<SkuHasStockVo> 类型,因为两个服务之间使用的是JSON来传输,
TypeReference<List<SkuHasStockVo>> typeReference = new TypeReference<List<SkuHasStockVo>>() {
};
//简写方式,收集获得库存List集合转化成一个Map集合,其中skuId作为key,stock作为value
stockMap = skuHasStock.getData(typeReference).stream()
.collect(Collectors.toMap(SkuHasStockVo::getSkuId, item -> item.getHasStock()));
} catch (Exception e) {
log.error("库存服务查询异常:原因{}", e);
}
Map<Long, Boolean> finalStockMap = stockMap;
//组合拼装上架的信息
List<SkuEsModel> collect = skuInfoEntities.stream().map(sku -> {
//组装需要的数据
SkuEsModel esModel = new SkuEsModel();
esModel.setSkuPrice(sku.getPrice());
esModel.setSkuImg(sku.getSkuDefaultImg());
// 设置库存信息,如果Map为空也表示有库存,不为空则根据该skuId来判断是否有库存
if (finalStockMap == null) {
esModel.setHasStock(true);
} else {
esModel.setHasStock(finalStockMap.get(sku.getSkuId()));
}
//先将物品的热度评分设置为0 TODO
esModel.setHotScore(0L);
//根据品牌Id 和分类Id 来查询 品牌和分类,并赋值给要上架的对象
BrandEntity brandEntity = brandService.getById(sku.getBrandId());
esModel.setBrandName(brandEntity.getName());
esModel.setBrandId(brandEntity.getBrandId());
esModel.setBrandImg(brandEntity.getLogo());
CategoryEntity categoryEntity = categoryService.getById(sku.getCatalogId());
esModel.setCatalogId(categoryEntity.getCatId());
esModel.setCatalogName(categoryEntity.getName());
// 设置检索属性
esModel.setAttrs(attrsList);
//将剩下对应的数据直接赋值
BeanUtils.copyProperties(sku, esModel);
return esModel;
}).collect(Collectors.toList());
// 远程调用,将数据发给es进行保存
R r = searchFeignService.productStatusUp(collect);
if (r.getCode() == 0) {
// 远程调用成功后,修改当前spu的状态
this.baseMapper.updateSpuStatus(spuId, WareConstant.StatusEnum.SPU_UP.getCode());
} else {
// 远程调用失败
// TODO 以后再来
}
}
远程调用 ware
Cotroller
// 远程调用查询是否还有库存
@PostMapping(value = "/hasStock")
public R getSkuHasStock(@RequestBody List<Long> skuIds) {
List<SkuHasStockVo> vos = wareSkuService.getSkusHasStock(skuIds);
//将获取的数据带回去
System.out.println(R.ok().setData(vos));
return R.ok().setData(vos);
}
public List<SkuHasStockVo> getSkusHasStock(List<Long> skuIds) {
//使用skuId通过遍历来找出所有的sku库存是否存在
List<SkuHasStockVo> collect = skuIds.stream().map(skuId -> {
SkuHasStockVo vo = new SkuHasStockVo();
Long count = baseMapper.getSkuStock(skuId);
vo.setSkuId(skuId);
//如果有库存就返回true即可。
vo.setHasStock(count ==null?false:count > 0);
return vo;
}).collect(Collectors.toList());
return collect;
}
远程调用 Search
Controller
@PostMapping("/product")//插入到es中
public R productStatusUp(@RequestBody List<SkuEsModel> skuEsModels){
boolean status = false;
status = elasticSaveService.productStatusUp(skuEsModels);
if (status) {
return R.error(BizCodeEnum.PRODUCT_UP_EXCEPTION.getCode(), BizCodeEnum.PRODUCT_UP_EXCEPTION.getMsg());
} else {
return R.ok();
}
}
Service
@Service
public class ElasticSaveServiceImpl implements ElasticSaveService {
@Autowired
RestHighLevelClient restHighLevelClient;
@Override
public boolean productStatusUp(List<SkuEsModel> skuEsModels) {
// 批量操作,建立es的映射
//在ES中保存这些数据
BulkRequest bulkRequest = new BulkRequest();
for (SkuEsModel skuEsModel : skuEsModels) {
//构造保存请求
IndexRequest indexRequest = new IndexRequest(EsConstant.PRODUCT_INDEX);
//插入时指定Id不指定会给默认值
indexRequest.id(skuEsModel.getSkuId().toString());
//将对象转化为JSON后插入
String jsonString = JSON.toJSONString(skuEsModel);
indexRequest.source(jsonString, XContentType.JSON);
bulkRequest.add(indexRequest);
}
//执行批量操作
BulkResponse bulk = null;
try {
bulk = restHighLevelClient.bulk(bulkRequest, ElasticSearchConfig.COMMON_OPTIONS);
} catch (IOException e) {
e.printStackTrace();
}
//如果批量错误,返回true
boolean hasFailures = bulk.hasFailures();
List<String> collect = Arrays.stream(bulk.getItems()).map(BulkItemResponse::getId).collect(Collectors.toList());
return hasFailures;
}
}
Thymeleaf
商城的前端页面使用Thymeleaf来写.且.前端页面省略…
配置Thymeleaf
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
//用来热部署,不需要每次都重启服务就可以看前端页面效果
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
一级分类的渲染
Controller
@GetMapping({"/","/index.html"}) //访问首页
public String indexPage(Model model){
//查找所有的一级分类
List<CategoryEntity> categoryEntits = categoryService.getLevel1Categorys();
model.addAttribute("categorys",categoryEntits);
return "index"; //字符串类型返回的是页面,thymeleaf会拼接成页面名
}
Service
public List<CategoryEntity> getLevel1Categorys() {
return baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("cat_level",1));
}
该部分对应的前端代码
<li th:each="category : ${categorys}">
<a href="#" class="header_main_left_a" th:attr="ctg-data=${category.catId}">
<b th:text="${category.name}">家用电器</b>
</a>
</li>
二级三级分类的渲染
创建实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Catelog2Vo {
private String catalog1Id; // 一级分类的ID
private List<Catelog3Vo> catalog3List;//三级分类的集合
private String id;
private String name;
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class Catelog3Vo{
private String catalog2Id; //二级分类的ID
private String id;
private String name;
}
}
Controller
@ResponseBody
@GetMapping("/index/catalog.json") //前端请求二三级分类所发的路径
public Map<String,List<Catelog2Vo>> getCatalogJson(){
System.out.println("-------------------------");
Map<String,List<Catelog2Vo>> map = categoryService.getCatalogJson();
return map;
}
Service
public Map<String, List<Catelog2Vo>> getCatalogJson() {
//获取所有一级分类
List<CategoryEntity> level1Categorys = getLevel1Categorys();
//将数据封装为MAP的形式
Map<String,List<Catelog2Vo>> parent_cid = level1Categorys.stream().collect(Collectors.toMap(key->key.getCatId().toString(),value->{
//查找所有的二级分类,因为一级分类的ID是二级分类的父ID
List<CategoryEntity> categoryEntities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", value.getCatId()));
List<Catelog2Vo> catelog2Vos = null;
//如果有二级分类
if(categoryEntities != null){
catelog2Vos = categoryEntities.stream().map(item ->{
//有参构造来New一个二级分类对象
Catelog2Vo catelog2Vo = new Catelog2Vo(value.getCatId().toString(),null,item.getCatId().toString(),item.getName());
//查找所有的三级分类,因为二级分类的ID是三级分类的父ID
List<CategoryEntity> level3Catalog = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid",item.getCatId()));
//如果有三级分类
if(level3Catalog != null){
List<Catelog2Vo.Catelog3Vo> collect = level3Catalog.stream().map(item3->{
//有参构造来New一个三级分类对象
Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(item.getCatId().toString(),item3.getCatId().toString(),item3.getName());
return catelog3Vo;
}).collect(Collectors.toList());
catelog2Vo.setCatalog3List(collect);
}
return catelog2Vo;
}).collect(Collectors.toList());
}
return catelog2Vos;
}));
return parent_cid;
}
缓存
Redis的配置
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置yaml
spring:
redis:
host: redis所在的主机Ip
port: redis端口号
将二三级分类的查询存入缓存
public Map<String, List<Catelog2Vo>> getCatalogJson() {
//先从缓存中获取
String catelogJson = redisTemplate.opsForValue().get("catalogJson");
//如果内存中没有,则从数据库中查询并放入内存
//都以JSON字符串的格式进行存储,方便跨平台,跨语言
if (StringUtils.isEmpty(catelogJson)){
//从数据库中查询,上面二三级分类改为getCatalogJsonDB方法
Map<String, List<Catelog2Vo>> catalogJsonDB = getCatalogJsonDB();
String s = JSON.toJSONString(catalogJsonDB);
redisTemplate.opsForValue().set("catalogJson",s);
return catalogJsonDB;
}
Map<String, List<Catelog2Vo>> stringCatelog2VoMap = JSON.parseObject(catelogJson, new TypeReference<Map<String, List<Catelog2Vo>>>(){});
return stringCatelog2VoMap;
}
缓存的穿透、雪崩、击穿
通俗理解:
①缓存穿透就是当缓存不存在的时候,需要去数据库中查,这个时候,如果几百万个请求(总之就是很多请求),同时访问缓存,因为缓存不存在,所以都会去访问数据库,数据库压力增大,失去了缓存的意义。
②缓存雪崩就是当很多的缓存同时失效,并且很多用户同时访问这些失效的缓存,这些请求都转到了数据库导致数据库压力过重。
③缓存击穿就是当许多用户来进行请求的时候,该缓存刚好失效,以至于这么多请求都会到数据库,就叫做缓存击穿。
分布式锁 Redisson
- 配置
yaml
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.12.0</version>
</dependency>
配置类
@Configuration
public class MyRedissonConfig {
@Bean(destroyMethod = "shutdown")
public RedissonClient redisson() throws IOException {
Config config = new Config();
config.useSingleServer().setAddress("redis://47.97.18.245:6379");
RedissonClient redissonClient = Redisson.create(config);
return redissonClient;
}
}
-
lock锁
①创建一个锁RLock lock = redissonClient.getLock("锁名");
②加锁
lock.lock();
阻塞式的等待,默认加的锁都是30S的时间
锁的自动续期:如果业务超长,运行期间会自动给锁续上新的30S,不需要担心业务时间长,锁会自动过期被删掉
加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认在30S以后自动删除
lock.lock(时间,单位);
lock.lock(10,TimeUnit.SECONDS)//相当于10秒自动解锁,自动解锁时间一定要大于业务的执行时间.
这样锁的话,在锁的时间到了后不会自动续期
如果规定了锁的超时时间,就发送给redis执行脚本,进行占锁,默认超时时间就是指定的时间
如果没指定锁的超时时间,就使用 30 * 1000 即30S,看门狗的默认时间(LockWatchdogTimeout)
只要占锁成功就会启动一个定时任务,重新设置锁的过期时间,新的过期时间就是看门狗的默认时间,每隔十秒都会再次续期,续为30S
一般在设置锁的时候,都会传递超时时间,省掉了整个续期操作,手动解锁
SpringCaChe
- 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
- yaml
spring:
cache:
type: redis # 缓存种类
redis:
time-to-live: 360000 #缓存时间
key-prefix: CACHE_ #缓存前缀名
use-key-prefix: true # 是否使用缓存前缀名
cache-null-values: true #缓存为null的时候是否设为null
- 在主启动类添加开启缓存的注解
@EnableCaching - 在想要添加缓存的地方加上注解
@Cacheable(value = {“前缀名”},key = “key,后缀”)
如下:前缀为category(域)且后缀为方法名命名的缓存,缓存内容为返回值
@Cacheable(value = {“category”},key = “#root.method.name”)
当该缓存对应的数据库发生改变的时候,删除该缓存
@CacheEvict(value = “category”, key = “‘getCatalogJson’”)
一次性指定多个缓存操作
@Caching(evict = {
@CacheEvict(value = “category”, key = “‘getLevel1Categorys’”),
@CacheEvict(value = “category”, key = “‘getCatalogJson’”)
}) - 因为默认的配置类在redis中格式有问题,所以需要自定义配置
@EnableConfigurationProperties(CacheProperties.class)
@Configuration
@EnableCaching
public class MyCacheConfig {
@Bean
RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties){
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
configuration = configuration.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
//读取配置文件的内容
CacheProperties.Redis redis = cacheProperties.getRedis();
if(redis.getTimeToLive() != null){
configuration = configuration.entryTtl(redis.getTimeToLive());
}
if(redis.getKeyPrefix() != null){
configuration = configuration.prefixKeysWith(redis.getKeyPrefix());
configuration = configuration.prefixCacheNameWith(redis.getKeyPrefix());
}
if(!redis.isCacheNullValues()){
configuration = configuration.disableCachingNullValues();
}
if(!redis.isUseKeyPrefix()){
configuration = configuration.disableKeyPrefix();
}
return configuration;
}
}
商城业务二
搜索、查找的对象封装
- 创建搜索的参数Vo和返回的结果Vo
搜索的参数Vo
public class SearchParam {
private Long catalog3Id; //三级分类id
private String keyword; //页面传递过来的全文匹配关键字
private List<Long> brandId; //品牌id,可以多选
private String sort; //排序条件:sort=price/salecount/hotscore_desc/asc
private Integer hasStock; //是否显示有货
private String skuPrice; //价格区间查询
private List<String> attrs; //按照属性进行筛选
private Integer pageNum = 1;//页码
private String _queryString; //原生的所有查询条件
}
返回结果的参数Vo
@Data
public class SearchResult {
private List<SkuEsModel> product; //查询到的所有商品信息
private Integer pageNum; //当前页码
private Long total; //总记录数
//总页码
private Integer totalPages;
private List<Integer> pageNavs;
private List<BrandVo> brands; //当前查询到的结果,所有涉及到的品牌
private List<AttrVo> attrs; //当前查询到的结果,所有涉及到的所有属性
private List<CatalogVo> catalogs; //当前查询到的结果,所有涉及到的所有分类
//===========================以上是返回给页面的所有信息============================//
/* 面包屑导航数据 */
private List<NavVo> navs;
@Data
public static class NavVo {
private String navName;
private String navValue;
private String link;
}
@Data
public static class BrandVo {
private Long brandId;
private String brandName;
private String brandImg;
}
@Data
public static class AttrVo {
private Long attrId;
private String attrName;
private List<String> attrValue;
}
@Data
public static class CatalogVo {
private Long catalogId;
private String catalogName;
}
检索业务核心代码
前端,略
封装页面响应Vo
package com.sky.wlmall.vo;
import lombok.Data;
@Data
public class AttrResponseVo {
/**
* 属性id
*/
private Long attrId;
/**
* 属性名
*/
private String attrName;
/**
* 是否需要检索[0-不需要,1-需要]
*/
private Integer searchType;
/**
* 属性图标
*/
private String icon;
/**
* 可选值列表[用逗号分隔]
*/
private String valueSelect;
/**
* 属性类型[0-销售属性,1-基本属性,2-既是销售属性又是基本属性]
*/
private Integer attrType;
/**
* 启用状态[0 - 禁用,1 - 启用]
*/
private Long enable;
/**
* 所属分类
*/
private Long catelogId;
/**
* 快速展示【是否展示在介绍上;0-否 1-是】,在sku中仍然可以调整
*/
private Integer showDesc;
private Long attrGroupId;
private String catelogName;
private String groupName;
private Long[] catelogPath;
}
Controller
@GetMapping("/index.html")
public String listPage(SearchParam searchParam, Model model, HttpServletRequest request){
searchParam.set_queryString(request.getQueryString());
SearchResult result = mallSearchService.search(searchParam);
model.addAttribute("result",result);
return "list";
}
@Slf4j
@Service
public class MallSearchServiceImpl implements MallSearchService {
@Autowired
private RestHighLevelClient esRestClient;
@Resource
private ProductFeignService productFeignService;
@Override
public SearchResult search(SearchParam param) {
// 动态构建出查询需要的DSL语句
SearchResult result = null;
//1、准备检索请求
SearchRequest searchRequest = buildSearchRequest(param);
try {
//2、执行检索请求
SearchResponse response = esRestClient.search(searchRequest, ElasticSearchConfig.COMMON_OPTIONS);
//3、分析响应数据,封装成我们需要的格式
result = buildSearchResult(response, param);
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
/**
* 构建结果数据
* 模糊匹配,过滤(按照属性、分类、品牌,价格区间,库存),完成排序、分页、高亮,聚合分析功能
*
* @param response
* @return
*/
private SearchResult buildSearchResult(SearchResponse response, SearchParam param) {
SearchResult result = new SearchResult();
//1、返回的所有查询到的商品
SearchHits hits = response.getHits();
List<SkuEsModel> esModels = new ArrayList<>();
//遍历所有商品信息
if (hits.getHits() != null && hits.getHits().length > 0) {
for (SearchHit hit : hits.getHits()) {
String sourceAsString = hit.getSourceAsString();
SkuEsModel esModel = JSON.parseObject(sourceAsString, SkuEsModel.class);
//判断是否按关键字检索,若是就显示高亮,否则不显示
if (!StringUtils.isEmpty(param.getKeyword())) {
//拿到高亮信息显示标题
HighlightField skuTitle = hit.getHighlightFields().get("skuTitle");
String skuTitleValue = skuTitle.getFragments()[0].string();
esModel.setSkuTitle(skuTitleValue);
}
esModels.add(esModel);
}
}
result.setProduct(esModels);
//2、当前商品涉及到的所有属性信息
List<SearchResult.AttrVo> attrVos = new ArrayList<>();
//获取属性信息的聚合
ParsedNested attrsAgg = response.getAggregations().get("attr_agg");
ParsedLongTerms attrIdAgg = attrsAgg.getAggregations().get("attr_id_agg");
for (Terms.Bucket bucket : attrIdAgg.getBuckets()) {
SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
//1、得到属性的id
long attrId = bucket.getKeyAsNumber().longValue();
attrVo.setAttrId(attrId);
//2、得到属性的名字
ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attr_name_agg");
String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
attrVo.setAttrName(attrName);
//3、得到属性的所有值
ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attr_value_agg");
List<String> attrValues = attrValueAgg.getBuckets().stream().map(MultiBucketsAggregation.Bucket::getKeyAsString).collect(Collectors.toList());
attrVo.setAttrValue(attrValues);
attrVos.add(attrVo);
}
result.setAttrs(attrVos);
//3、当前商品涉及到的所有品牌信息
List<SearchResult.BrandVo> brandVos = new ArrayList<>();
//获取到品牌的聚合
ParsedLongTerms brandAgg = response.getAggregations().get("brand_agg");
for (Terms.Bucket bucket : brandAgg.getBuckets()) {
SearchResult.BrandVo brandVo = new SearchResult.BrandVo();
//1、得到品牌的id
long brandId = bucket.getKeyAsNumber().longValue();
brandVo.setBrandId(brandId);
//2、得到品牌的名字
ParsedStringTerms brandNameAgg = bucket.getAggregations().get("brand_name_agg");
String brandName = brandNameAgg.getBuckets().get(0).getKeyAsString();
brandVo.setBrandName(brandName);
//3、得到品牌的图片
ParsedStringTerms brandImgAgg = bucket.getAggregations().get("brand_img_agg");
String brandImg = brandImgAgg.getBuckets().get(0).getKeyAsString();
brandVo.setBrandImg(brandImg);
brandVos.add(brandVo);
}
result.setBrands(brandVos);
//4、当前商品涉及到的所有分类信息
//获取到分类的聚合
List<SearchResult.CatalogVo> catalogVos = new ArrayList<>();
ParsedLongTerms catalogAgg = response.getAggregations().get("catalog_agg");
for (Terms.Bucket bucket : catalogAgg.getBuckets()) {
SearchResult.CatalogVo catalogVo = new SearchResult.CatalogVo();
//得到分类id
String keyAsString = bucket.getKeyAsString();
catalogVo.setCatalogId(Long.parseLong(keyAsString));
//得到分类名
ParsedStringTerms catalogNameAgg = bucket.getAggregations().get("catalog_name_agg");
String catalogName = catalogNameAgg.getBuckets().get(0).getKeyAsString();
catalogVo.setCatalogName(catalogName);
catalogVos.add(catalogVo);
}
result.setCatalogs(catalogVos);
//===============以上可以从聚合信息中获取====================//
//5、分页信息-页码
result.setPageNum(param.getPageNum());
//5、1分页信息、总记录数
long total = hits.getTotalHits().value;
result.setTotal(total);
//5、2分页信息-总页码-计算
int totalPages = (int) total % EsConstant.PRODUCT_PAGESIZE == 0 ?
(int) total / EsConstant.PRODUCT_PAGESIZE : ((int) total / EsConstant.PRODUCT_PAGESIZE + 1);
result.setTotalPages(totalPages);
List<Integer> pageNavs = new ArrayList<>();
for (int i = 1; i <= totalPages; i++) {
pageNavs.add(i);
}
result.setPageNavs(pageNavs);
//6、构建面包屑导航
if (param.getAttrs() != null && param.getAttrs().size() > 0) {
List<SearchResult.NavVo> collect = param.getAttrs().stream().map(attr -> {
//1、分析每一个attrs传过来的参数值
SearchResult.NavVo navVo = new SearchResult.NavVo();
String[] s = attr.split("_");
navVo.setNavValue(s[1]);
R r = productFeignService.attrInfo(Long.parseLong(s[0]));
if (r.getCode() == 0) {
AttrResponseVo data = r.getData("attr", new TypeReference<AttrResponseVo>() {
});
navVo.setNavName(data.getAttrName());
} else {
navVo.setNavName(s[0]);
}
//2、取消了这个面包屑以后,我们要跳转到哪个地方,将请求的地址url里面的当前置空
//拿到所有的查询条件,去掉当前
String encode = null;
try {
encode = URLEncoder.encode(attr, "UTF-8");
encode.replace("+", "%20"); //浏览器对空格的编码和Java不一样,差异化处理
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
String replace = param.get_queryString().replace("&attrs=" + attr, "");
navVo.setLink("http://127.0.0.1:12000/index.html?" + replace);
return navVo;
}).collect(Collectors.toList());
result.setNavs(collect);
}
return result;
}
/**
* 准备检索请求
* 模糊匹配,过滤(按照属性,分类,品牌,价格区间,库存),排序,分页,高亮,聚合分析
*
* @return
*/
private SearchRequest buildSearchRequest(SearchParam param) {
// 检索请求构建
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
/**
* 查询:模糊匹配,过滤(按照属性,分类,品牌,价格区间,库存)
*/
//1. 构建 bool-query
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
//1.1 bool-must 模糊匹配
if (!StringUtils.isEmpty(param.getKeyword())) {
boolQueryBuilder.must(QueryBuilders.matchQuery("skuTitle", param.getKeyword()));
}
//1.2.1 bool-filter catalogId 按照三级分类id查询
if (null != param.getCatalog3Id()) {
boolQueryBuilder.filter(QueryBuilders.termQuery("catalogId", param.getCatalog3Id()));
}
//1.2.2 bool-filter brandId 按照品牌id查询
if (null != param.getBrandId() && param.getBrandId().size() > 0) {
boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId", param.getBrandId()));
}
//1.2.3 bool-filter attrs 按照指定的属性查询
if (param.getAttrs() != null && param.getAttrs().size() > 0) {
param.getAttrs().forEach(item -> {
//attrs=1_5寸:8寸&2_16G:8G
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//attrs=1_5寸:8寸
String[] s = item.split("_");
String attrId = s[0]; // 检索的属性id
String[] attrValues = s[1].split(":");//这个属性检索用的值
boolQuery.must(QueryBuilders.termQuery("attrs.attrId", attrId));
boolQuery.must(QueryBuilders.termsQuery("attrs.attrValue", attrValues));
// 每一个属性都要生成一个 nested 查询
NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery("attrs", boolQuery, ScoreMode.None);
boolQueryBuilder.filter(nestedQueryBuilder);
});
}
//1.2.4 bool-filter hasStock 按照是否有库存查询
if (null != param.getHasStock()) {
boolQueryBuilder.filter(QueryBuilders.termQuery("hasStock", param.getHasStock() == 1));
}
//1.2.5 skuPrice bool-filter 按照价格区间查询
if (!StringUtils.isEmpty(param.getSkuPrice())) {
//skuPrice形式为:1_500或_500或500_
RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("skuPrice");
String[] price = param.getSkuPrice().split("_");
if (price.length == 2) {
rangeQueryBuilder.gte(price[0]).lte(price[1]);
} else if (price.length == 1) {
if (param.getSkuPrice().startsWith("_")) {
rangeQueryBuilder.lte(price[1]);
}
if (param.getSkuPrice().endsWith("_")) {
rangeQueryBuilder.gte(price[0]);
}
}
boolQueryBuilder.filter(rangeQueryBuilder);
}
// 封装所有的查询条件
searchSourceBuilder.query(boolQueryBuilder);
/**
* 排序,分页,高亮
*/
// 2.1 排序 形式为sort=hotScore_asc/desc
if (!StringUtils.isEmpty(param.getSort())) {
String sort = param.getSort();
// sort=hotScore_asc/desc
String[] sortFields = sort.split("_");
SortOrder sortOrder = "asc".equalsIgnoreCase(sortFields[1]) ? SortOrder.ASC : SortOrder.DESC;
searchSourceBuilder.sort(sortFields[0], sortOrder);
}
// 2.2 分页 from = (pageNum - 1) * pageSize
searchSourceBuilder.from((param.getPageNum() - 1) * EsConstant.PRODUCT_PAGESIZE);
searchSourceBuilder.size(EsConstant.PRODUCT_PAGESIZE);
// 2.3 高亮
if (!StringUtils.isEmpty(param.getKeyword())) {
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("skuTitle");
highlightBuilder.preTags("<b style='color:red'>");
highlightBuilder.postTags("</b>");
searchSourceBuilder.highlighter(highlightBuilder);
}
System.out.println("构建的DSL语句" + searchSourceBuilder.toString());
/**
* 聚合分析
*/
//1. 按照品牌进行聚合
TermsAggregationBuilder brand_agg = AggregationBuilders.terms("brand_agg");
brand_agg.field("brandId").size(50);
//1.1 品牌的子聚合-品牌名聚合
brand_agg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName").size(1));
//1.2 品牌的子聚合-品牌图片聚合
brand_agg.subAggregation(AggregationBuilders.terms("brand_img_agg").field("brandImg").size(1));
searchSourceBuilder.aggregation(brand_agg);
//2. 按照分类信息进行聚合
TermsAggregationBuilder catalog_agg = AggregationBuilders.terms("catalog_agg");
catalog_agg.field("catalogId").size(20);
catalog_agg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(1));
searchSourceBuilder.aggregation(catalog_agg);
// 3. 按照属性信息进行聚合
NestedAggregationBuilder attr_agg = AggregationBuilders.nested("attr_agg", "attrs");
//3.1 按照属性ID进行聚合
TermsAggregationBuilder attr_id_agg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId");
attr_agg.subAggregation(attr_id_agg);
//3.1.1 在每个属性ID下,按照属性名进行聚合
attr_id_agg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(1));
//3.1.2 在每个属性ID下,按照属性值进行聚合
attr_id_agg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(50));
searchSourceBuilder.aggregation(attr_agg);
log.debug("构建的DSL语句 {}", searchSourceBuilder.toString());
SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX}, searchSourceBuilder);
return searchRequest;
}
}
详情页展示核心代码
Controller
@GetMapping("/{skuId}.html")
public String skuItem(@PathVariable("skuId") Long skuId, Model model){
SkuItemVo skuItemVo = skuInfoService.item(skuId);
model.addAttribute("item",skuItemVo);
System.out.println(skuItemVo);
return "item";
}
Service
public SkuItemVo item(Long skuId) {
SkuItemVo skuItemVo = new SkuItemVo();
CompletableFuture<SkuInfoEntity> infoFuture = CompletableFuture.supplyAsync(()->{
SkuInfoEntity info = getById(skuId);
skuItemVo.setInfo(info);
return info;
},executor);
CompletableFuture<Void> saleAttrFuture = infoFuture.thenAcceptAsync((res) -> {
List<SkuItemSaleAttrVo> saleAttrVos = skuSaleAttrValueService.getSaleAttrsBySpuId(res.getSpuId());
skuItemVo.setSaleAttr(saleAttrVos);
}, executor);
CompletableFuture<Void> descFuture = infoFuture.thenAcceptAsync(res -> {
SpuInfoDescEntity spuInfoDescEntity = spuInfoDescService.getById(res.getSpuId());
skuItemVo.setDesc(spuInfoDescEntity);
}, executor);
CompletableFuture<Void> baseAttrFuture = infoFuture.thenAcceptAsync(res -> {
List<SpuItemAttrGroupVo> attrGroupVos = attrGroupService.getAttrGroupWithAttrsBySpuId(res.getSpuId(), res.getCatalogId());
skuItemVo.setGroupAttrs(attrGroupVos);
}, executor);
CompletableFuture<Void> imageFuture = CompletableFuture.runAsync(() -> {
List<SkuImagesEntity> imagesEntities = skuImagesService.getImagesBySkuId(skuId);
skuItemVo.setImages(imagesEntities);
}, executor);
try {
CompletableFuture.allOf(saleAttrFuture, descFuture, baseAttrFuture, imageFuture).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
return skuItemVo;
}
短信认证注册用户与登录
- 写的时候忘了记了…现在不想记了
购物车功能
- 创建购物车微服务\前端页面…略
- 创建购物车的VO
购物车项
public class CartVo {
/**
* 购物车子项信息
*/
List<CartItemVo> items;
/**
* 商品数量
*/
private Integer countNum;
/**
* 商品类型数量
*/
private Integer countType;
/**
* 商品总价
*/
private BigDecimal totalAmount;
/**
* 减免价格
*/
private BigDecimal reduce = new BigDecimal("0.00");;
public List<CartItemVo> getItems() {
return items;
}
public void setItems(List<CartItemVo> items) {
this.items = items;
}
public Integer getCountNum() {
int count = 0;
if (items != null && items.size() > 0) {
for (CartItemVo item : items) {
count += item.getCount();
}
}
return count;
}
public Integer getCountType() {
int count = 0;
if (items != null && items.size() > 0) {
for (CartItemVo item : items) {
count += 1;
}
}
return count;
}
public BigDecimal getTotalAmount() {
BigDecimal amount = new BigDecimal("0");
// 计算购物项总价
if (!CollectionUtils.isEmpty(items)) {
for (CartItemVo cartItem : items) {
if (cartItem.getCheck()) {
amount = amount.add(cartItem.getTotalPrice());
}
}
}
// 计算优惠后的价格
return amount.subtract(getReduce());
}
public BigDecimal getReduce() {
return reduce;
}
public void setReduce(BigDecimal reduce) {
this.reduce = reduce;
}
}
购物车子项
public class CartItemVo {
private Long skuId;
private Boolean check = true;
private String title;
private String image;
/**
* 商品套餐属性
*/
private List<String> skuAttrValues;
private BigDecimal price;
private Integer count;
private BigDecimal totalPrice;
public Long getSkuId() {
return skuId;
}
public void setSkuId(Long skuId) {
this.skuId = skuId;
}
public Boolean getCheck() {
return check;
}
public void setCheck(Boolean check) {
this.check = check;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
public List<String> getSkuAttrValues() {
return skuAttrValues;
}
public void setSkuAttrValues(List<String> skuAttrValues) {
this.skuAttrValues = skuAttrValues;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
/**
* 计算当前购物项总价
*
* @return
*/
public BigDecimal getTotalPrice() {
return this.price.multiply(new BigDecimal("" + this.count));
}
public void setTotalPrice(BigDecimal totalPrice) {
this.totalPrice = totalPrice;
}
}
-
配置
①配置redis缓存(将购物车的东西存到缓存中)
②配置SpingSession来共享登录时的Session<dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency>
@Configuration public class WlmallSessionConfig { @Bean public CookieSerializer cookieSerializer() { DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer(); //放大作用域 cookieSerializer.setDomainName("127.0.0.1"); cookieSerializer.setCookieName("WLSESSION"); return cookieSerializer; } @Bean public RedisSerializer<Object> springSessionDefaultRedisSerializer() { return new GenericJackson2JsonRedisSerializer(); } }
主启动类中开启@EnableRedisHttpSession
-
创建用户的To
public class UserInfoTo {
private Long userId;
private String userKey;
/**
* 是否临时用户
*/
private Boolean tempUser = false;
}
- 设置拦截器、ThreadLocal、全局Session和线程池
public class CartInterceptor implements HandlerInterceptor {
//线程共享数据
public static ThreadLocal<UserInfoTo> toThreadLocal = new ThreadLocal<>();
/***
* 目标方法执行之前
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
UserInfoTo userInfoTo = new UserInfoTo();
HttpSession session = request.getSession();
//获得当前登录用户的信息
MemberResponseVo memberResponseVo = (MemberResponseVo) session.getAttribute(AuthServerConstant.LOGIN_USER);
if (memberResponseVo != null) {
//用户登录了
userInfoTo.setUserId(memberResponseVo.getId());
}
//如果用户没有登录也会给一个Cookie来标识购物车
Cookie[] cookies = request.getCookies();
if (cookies != null && cookies.length > 0) {
for (Cookie cookie : cookies) {
//user-key
String name = cookie.getName();
if (name.equals(CartConstant.TEMP_USER_COOKIE_NAME)) {
userInfoTo.setUserKey(cookie.getValue());
//标记为已是临时用户
userInfoTo.setTempUser(true);
}
}
}
//如果没有临时用户一定分配一个临时用户
if (StringUtils.isEmpty(userInfoTo.getUserKey())) {
String uuid = UUID.randomUUID().toString();
userInfoTo.setUserKey(uuid);
}
//目标方法执行之前
toThreadLocal.set(userInfoTo);
return true;
}
/**
* 业务执行之后,分配临时用户来浏览器保存
*
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
//获取当前用户的值
UserInfoTo userInfoTo = toThreadLocal.get();
//如果没有临时用户一定保存一个临时用户
if (!userInfoTo.getTempUser()) {
//创建一个cookie
Cookie cookie = new Cookie(CartConstant.TEMP_USER_COOKIE_NAME, userInfoTo.getUserKey());
//扩大作用域
cookie.setDomain("127.0.0.1");
//设置过期时间
cookie.setMaxAge(CartConstant.TEMP_USER_COOKIE_TIMEOUT);
response.addCookie(cookie);
}
}
}
@Configuration
public class WlmallWebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CartInterceptor())//注册拦截器
.addPathPatterns("/**");
}
}
@Configuration
public class WlmallSessionConfig {
@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
//放大作用域
cookieSerializer.setDomainName("127.0.0.1");
cookieSerializer.setCookieName("WLSESSION");
return cookieSerializer;
}
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
return new GenericJackson2JsonRedisSerializer();
}
}
@Configuration
public class MyThreadConfig {
@Bean
public ThreadPoolExecutor threadPoolExecutor(ThreadPoolConfigProperties pool){
return new ThreadPoolExecutor(
pool.getCoreSize(),pool.getMaxSize(),pool.getKeepAliveTime(), TimeUnit.SECONDS,
new LinkedBlockingDeque<>(100000),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
}
}
@ConfigurationProperties(prefix = "wlmall.thread")
@Component
@Data
public class ThreadPoolConfigProperties {
private Integer coreSize;
private Integer maxSize;
private Integer keepAliveTime;
}
部分
Controller
@Controller
public class CartController {
@Autowired
private CartService cartService;
/**进入购物车
*
* @param model
* @return
* @throws ExecutionException
* @throws InterruptedException
*/
@GetMapping( "/cart.html")
public String cartListPage(Model model) throws ExecutionException, InterruptedException {
//快速得到用户信息:id,user-key
UserInfoTo userInfoTo = CartInterceptor.toThreadLocal.get();
CartVo cartVo = cartService.getCart();
model.addAttribute("cart", cartVo);
return "cartList";
}
/**
* 添加商品到购物车
* attributes.addFlashAttribute():将数据放在session中,可以在页面中取出,但是只能取一次
* attributes.addAttribute():将数据放在url后面
*
* @return
*/
@GetMapping(value = "/addToCart")
public String addCartItem(@RequestParam("skuId") Long skuId,
@RequestParam("num") Integer num,
RedirectAttributes attributes) throws ExecutionException, InterruptedException {
cartService.addToCart(skuId, num);
attributes.addAttribute("skuId", skuId);
return "redirect:http://127.0.0.1:21000/addToCartSuccessPage.html";
}
/**
* 跳转到添加购物车成功页面
*
* @param skuId
* @param model
* @return
*/
@GetMapping(value = "/addToCartSuccessPage.html")
public String addToCartSuccessPage(@RequestParam("skuId") Long skuId,
Model model) {
//重定向到成功页面。再次查询购物车数据即可
CartItemVo cartItemVo = cartService.getCartItem(skuId);
model.addAttribute("cartItem", cartItemVo);
return "success";
}
/**通过复选框勾选选项
*
* @param skuId
* @param checked
* @return
*/
@GetMapping(value = "/checkItem")
public String checkItem(@RequestParam(value = "skuId") Long skuId,
@RequestParam(value = "checked") Integer checked) {
cartService.checkItem(skuId, checked);
return "redirect:http://127.0.0.1:21000/cart.html";
}
/**
* 改变商品数量
*
* @param skuId
* @param num
* @return
*/
@GetMapping(value = "/countItem")
public String countItem(@RequestParam(value = "skuId") Long skuId,
@RequestParam(value = "num") Integer num) {
cartService.changeItemCount(skuId, num);
return "redirect:http://127.0.0.1:21000/cart.html";
}
/**
* 删除商品信息
*
* @param skuId
* @return
*/
@GetMapping(value = "/deleteItem")
public String deleteItem(@RequestParam("skuId") Integer skuId) {
cartService.deleteIdCartInfo(skuId);
return "redirect:http://127.0.0.1:21000/cart.html";
}
}
Service
@Service
public class CartServiceImpl implements CartService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private ProductFeignService productFeignService;
@Autowired
ThreadPoolExecutor executor;
private final String CART_PREFIX = "wlmall:cart:";
@Override
public CartVo getCart(){
CartVo cartVo = new CartVo();
UserInfoTo userInfoTo = CartInterceptor.toThreadLocal.get();
if (userInfoTo.getUserId() != null) {
//1、登录
String cartKey = CART_PREFIX + userInfoTo.getUserId();
//临时购物车的键
String temptCartKey = CART_PREFIX + userInfoTo.getUserKey();
//2、如果临时购物车的数据还未进行合并
List<CartItemVo> tempCartItems = getCartItems(temptCartKey);
if (tempCartItems != null) {
//临时购物车有数据需要进行合并操作
for (CartItemVo item : tempCartItems) {
addToCart(item.getSkuId(), item.getCount());
}
//清除临时购物车的数据
clearCartInfo(temptCartKey);
}
//3、获取登录后的购物车数据【包含合并过来的临时购物车的数据和登录后购物车的数据】
List<CartItemVo> cartItems = getCartItems(cartKey);
cartVo.setItems(cartItems);
} else {
//没登录
String cartKey = CART_PREFIX + userInfoTo.getUserKey();
//获取临时购物车里面的所有购物项
List<CartItemVo> cartItems = getCartItems(cartKey);
cartVo.setItems(cartItems);
}
return cartVo;
}
@Override
public CartItemVo addToCart(Long skuId, Integer num) {
//拿到要操作的购物车信息
BoundHashOperations<String, Object, Object> cartOps = getCartOps();
//判断Redis是否有该商品的信息
String productRedisValue = (String) cartOps.get(skuId.toString());
//如果没有就添加数据
if (StringUtils.isEmpty(productRedisValue)) {
//添加新的商品到购物车
CartItemVo cartItemVo = new CartItemVo();
//开启第一个异步任务
CompletableFuture<Void> getSkuInfoFuture = CompletableFuture.runAsync(() -> {
//远程查询当前要添加商品的信息
R productSkuInfo = productFeignService.getInfo(skuId);
SkuInfoVo skuInfo = productSkuInfo.getData("skuInfo", new TypeReference<SkuInfoVo>() {
});
//数据赋值操作
cartItemVo.setSkuId(skuInfo.getSkuId());
cartItemVo.setTitle(skuInfo.getSkuTitle());
cartItemVo.setImage(skuInfo.getSkuDefaultImg());
cartItemVo.setPrice(skuInfo.getPrice());
cartItemVo.setCount(num);
}, executor);
//开启第二个异步任务
CompletableFuture<Void> getSkuAttrValuesFuture = CompletableFuture.runAsync(() -> {
//2、远程查询skuAttrValues组合信息
List<String> skuSaleAttrValues = productFeignService.getSkuSaleAttrValues(skuId);
cartItemVo.setSkuAttrValues(skuSaleAttrValues);
}, executor);
//等待所有的异步任务全部完成
try {
CompletableFuture.allOf(getSkuInfoFuture, getSkuAttrValuesFuture).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
String cartItemJson = JSON.toJSONString(cartItemVo);
cartOps.put(skuId.toString(), cartItemJson);
return cartItemVo;
} else {
//购物车有此商品,修改数量即可
CartItemVo cartItemVo = JSON.parseObject(productRedisValue, CartItemVo.class);
cartItemVo.setCount(cartItemVo.getCount() + num);
//修改redis的数据
String cartItemJson = JSON.toJSONString(cartItemVo);
cartOps.put(skuId.toString(), cartItemJson);
return cartItemVo;
}
}
@Override
public CartItemVo getCartItem(Long skuId) {
//拿到要操作的购物车信息
BoundHashOperations<String, Object, Object> cartOps = getCartOps();
String redisValue = (String) cartOps.get(skuId.toString());
return JSON.parseObject(redisValue, CartItemVo.class);
}
@Override
public void checkItem(Long skuId, Integer checked) {
//查询购物车里面的商品
CartItemVo cartItem = getCartItem(skuId);
//修改商品状态
cartItem.setCheck(checked == 1);
//序列化存入redis中
String redisValue = JSON.toJSONString(cartItem);
BoundHashOperations<String, Object, Object> cartOps = getCartOps();
cartOps.put(skuId.toString(), redisValue);
}
@Override
public void changeItemCount(Long skuId, Integer num) {
//查询购物车里面的商品
CartItemVo cartItem = getCartItem(skuId);
cartItem.setCount(num);
BoundHashOperations<String, Object, Object> cartOps = getCartOps();
//序列化存入redis中
String redisValue = JSON.toJSONString(cartItem);
cartOps.put(skuId.toString(), redisValue);
}
@Override
public void deleteIdCartInfo(Integer skuId) {
BoundHashOperations<String, Object, Object> cartOps = getCartOps();
cartOps.delete(skuId.toString());
}
private BoundHashOperations<String, Object, Object> getCartOps() {
//先得到当前用户信息
UserInfoTo userInfoTo = CartInterceptor.toThreadLocal.get();
String cartKey = "";
if (userInfoTo.getUserId() != null) {
cartKey = CART_PREFIX + userInfoTo.getUserId();
} else {
cartKey = CART_PREFIX + userInfoTo.getUserKey();
}
//绑定指定的key操作Redis
return stringRedisTemplate.boundHashOps(cartKey);
}
/**
* 获取购物车里面的数据
*
* @param cartKey
* @return
*/
private List<CartItemVo> getCartItems(String cartKey) {
//获取购物车里面的所有商品
BoundHashOperations<String, Object, Object> operations = stringRedisTemplate.boundHashOps(cartKey);
List<Object> values = operations.values();
if (values != null && values.size() > 0) {
return values.stream().map((obj) -> {
String str = (String) obj;
return JSON.parseObject(str, CartItemVo.class);
}).collect(Collectors.toList());
}
return null;
}
public void clearCartInfo(String cartKey) {
stringRedisTemplate.delete(cartKey);
}
}
RabbitMQ
- 安装
docker run -d --name rabbitmq -p 5671:5671 -p 5672:5672 -p 4369:4369 -p 25672:25672 -p 15671:15671 -p 15672:15672 rabbitmq:management
Unable to find image 'rabbitmq:management' locally
- 整合到SpringBoot
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
spring.rabbitmq.host=Ip
spring.rabbitmq.port=5672
spring.rabbitmq.virtual-host=/
spring.rabbitmq.publisher-confirm-type=correlated
# 开启发送端消息抵达队列的确认
spring.rabbitmq.publisher-returns=true
#只要抵达队列,以异步发送优先回调我们这个return
spring.rabbitmq.template.mandatory=true
#手动ack
spring.rabbitmq.listener.simple.acknowledge-mode=manual
spring.main.allow-circular-references = true
主启动类开启消息队列
@EnableRabbit
测试使用
@Autowired
AmqpAdmin amqpAdmin;
@Test
void contextLoads() {
//创建exchange
DirectExchange directExchange = new DirectExchange("hello-java-exchange",true,false);
amqpAdmin.declareExchange(directExchange);
//创建队列
Queue queue = new Queue("hello-java-queue",true,false,false);
amqpAdmin.declareQueue(queue);
//绑定两者
Binding binding = new Binding("hello-java-queue",Binding.DestinationType.QUEUE,"hello-java-exchange","hello.java",null);
amqpAdmin.declareBinding(binding);
//发送信息
String msg = "hello word!";
rabbitTemplate.convertAndSend("hello-java-exchange","hello.java",msg);
}
如果传一个对象信息,可以将该对象序列号后传入,或者传json格式,需要如下配置
@Configuration
public class MyRabbitConfig {
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
}
可靠传输设置
spring.rabbitmq.publisher-confirm-type=correlated
# 开启发送端消息抵达队列的确认
spring.rabbitmq.publisher-returns=true
#只要抵达队列,以异步发送优先回调我们这个return
spring.rabbitmq.template.mandatory=true
#手动ack
spring.rabbitmq.listener.simple.acknowledge-mode=manual
@Configuration
public class MyRabbitConfig {
@Autowired
RabbitTemplate rabbitTemplate;
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
@PostConstruct //对象创建完成后,执行该方法
public void initRabbitTemplate(){
//设置确认回调
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean b, String s) {
}
});
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
@Override
public void returnedMessage(Message message, int i, String s, String s1, String s2) {
}
});
}
}
订单服务
略