Bladeren bron

:alarm_clock: 商品服务,搜索后端

nnkwrik 6 jaren geleden
bovenliggende
commit
ae012021f6

+ 1 - 0
.gitignore

@@ -12,6 +12,7 @@
 
 # other
 dev/mysql/data/*
+dev/mysql/log/*
 auth-service/src/main/resources/application-secret.yml
 **/*.key
 **/*.pub

+ 6 - 0
dev/mysql/conf/my.cnf

@@ -2,6 +2,12 @@
 character-set-server=utf8mb4
 default-time_zone=+8:00
 
+server_id = 1
+log_bin = /var/log/mysql/mysql-bin.log
+max_binlog_size = 1000M
+binlog-format = row
+
+
 [client]
 default-character-set=utf8mb4
 

+ 6 - 1
dev/mysql/shart.sh

@@ -2,4 +2,9 @@
 cur_dir=`pwd`
 docker stop fangxianyu-mysql
 docker rm fangxianyu-mysql
-docker run -d --name fangxianyu-mysql -v ${cur_dir}/conf:/etc/mysql/conf.d -v ${cur_dir}/data:/var/lib/mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=1234  mysql:5.7
+
+docker run -d --name fangxianyu-mysql \
+-v ${cur_dir}/conf:/etc/mysql/conf.d \
+-v ${cur_dir}/data:/var/lib/mysql \
+-v ${cur_dir}/log:/var/log/mysql/mysql-bin.log \
+-p 3306:3306 -e MYSQL_ROOT_PASSWORD=1234  mysql:5.7

+ 1 - 2
goods-service/src/main/java/io/github/nnkwrik/goodsservice/GoodsServiceApplication.java

@@ -3,8 +3,7 @@ package io.github.nnkwrik.goodsservice;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 
-@SpringBootApplication
-
+@SpringBootApplication(scanBasePackages = "io.github.nnkwrik")
 public class GoodsServiceApplication {
 
     public static void main(String[] args) {

+ 16 - 14
goods-service/src/main/java/io/github/nnkwrik/goodsservice/cache/BrowseCache.java

@@ -1,7 +1,10 @@
 package io.github.nnkwrik.goodsservice.cache;
 
-import com.google.common.cache.*;
-import io.github.nnkwrik.goodsservice.dao.OtherMapper;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.RemovalListener;
+import com.google.common.cache.RemovalListeners;
+import io.github.nnkwrik.goodsservice.dao.GoodsMapper;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
@@ -22,9 +25,7 @@ import java.util.concurrent.atomic.AtomicInteger;
 public class BrowseCache {
 
     @Autowired
-    private OtherMapper otherMapper;
-
-    private static BrowseCache browseCache = new BrowseCache(); //单例
+    private GoodsMapper goodsMapper;
 
 
     //过期(1天)/超出缓存队列大小(1000)/缓存超过10个时 会触发
@@ -32,7 +33,7 @@ public class BrowseCache {
             notification -> {
                 log.info("BrowseCache缓存刷入数据库,原因 :【{}】,数据 :【key={} , value={}】",
                         notification.getCause(), notification.getKey(), notification.getValue().get());
-                otherMapper.addBrowseCount(notification.getKey(), notification.getValue().get());
+                goodsMapper.addBrowseCount(notification.getKey(), notification.getValue().get());
             };
 
 
@@ -49,22 +50,23 @@ public class BrowseCache {
                     .build();
 
 
-    public static void add(Integer goodsId) {
-        Cache<Integer, AtomicInteger> singleCache =  browseCache.cache;
-        AtomicInteger browseCount = singleCache.getIfPresent(goodsId);
+    public void add(Integer goodsId) {
+        int count = 0;
+        AtomicInteger browseCount = cache.getIfPresent(goodsId);
         if (browseCount != null) {
-            int count = browseCount.incrementAndGet();
+            count = browseCount.incrementAndGet();
             if (count > 10) {
                 //手动移除缓存,让他触发removalListener,刷新数据库
-                singleCache.invalidate(goodsId);
+                cache.invalidate(goodsId);
             }
         } else {
-            singleCache.put(goodsId, new AtomicInteger(1));
+            count = 1;
+            cache.put(goodsId, new AtomicInteger(1));
         }
-
+        log.debug("BrowseCache更新商品id【{}】的浏览次数为【{}】", goodsId, count);
         //检查过期的缓存,让他触发removalListener
         //必须执行这个cache才会去检查是否过期, 否则尽管过期他也不会触发removalListener
-        singleCache.cleanUp();
+        cache.cleanUp();
     }
 
 

+ 7 - 5
goods-service/src/main/java/io/github/nnkwrik/goodsservice/cache/SearchCache.java

@@ -3,6 +3,7 @@ package io.github.nnkwrik.goodsservice.cache;
 import com.google.common.cache.Cache;
 import com.google.common.cache.CacheBuilder;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
 
 import java.util.Comparator;
 import java.util.List;
@@ -18,31 +19,32 @@ import java.util.stream.Collectors;
  * @date 18/11/20 15:23
  */
 @Slf4j
+@Component
 public class SearchCache {
     /**
      * @key 搜索关键字 TODO 分词
      * @value 搜索次数
      */
-    private static Cache<String, AtomicInteger> cache =
+    private Cache<String, AtomicInteger> cache =
             CacheBuilder.newBuilder()
                     .maximumSize(10000) //超出大小时,替换最久没更新value的key
                     .expireAfterWrite(30, TimeUnit.DAYS)
                     .build();
 
-    public static void add(String keyword) {
-
+    public void add(String keyword) {
         int count = 0;
         AtomicInteger browseCount = cache.getIfPresent(keyword);
         if (browseCount != null) {
             count = browseCount.incrementAndGet();
         } else {
+            count =1;
             cache.put(keyword, new AtomicInteger(1));
         }
 
-        log.debug("SearchCache更新关键字【{}】的搜索次数为【{}】",keyword,count);
+        log.debug("SearchCache更新关键字【{}】的搜索次数为【{}】", keyword, count);
     }
 
-    public static List<String> getHot(int num) {
+    public List<String> getHot(int num) {
         log.debug("从SearchCache获取热门关键字列表");
         return cache.asMap().entrySet().stream()
                 .sorted(Comparator.comparingInt(entry -> -entry.getValue().get()))

+ 13 - 13
goods-service/src/main/java/io/github/nnkwrik/goodsservice/controller/GoodsController.java

@@ -3,7 +3,7 @@ package io.github.nnkwrik.goodsservice.controller;
 import io.github.nnkwrik.goodsservice.model.vo.CategoryPageVo;
 import io.github.nnkwrik.goodsservice.model.vo.GoodsDetailPageVo;
 import io.github.nnkwrik.goodsservice.model.vo.GoodsRelatedVo;
-import io.github.nnkwrik.goodsservice.model.vo.ResponseVO;
+import io.github.nnkwrik.common.dto.Response;
 import io.github.nnkwrik.goodsservice.model.vo.inner.CategoryVo;
 import io.github.nnkwrik.goodsservice.model.vo.inner.GalleryVo;
 import io.github.nnkwrik.goodsservice.model.vo.inner.GoodsDetailVo;
@@ -36,29 +36,29 @@ public class GoodsController {
      */
     @GetMapping("/goods/category/{categoryId}")
 
-    public ResponseVO<CategoryVo> getCategoryPage(@PathVariable("categoryId") int categoryId,
-                                                  @RequestParam(value = "page", defaultValue = "1") int page,
-                                                  @RequestParam(value = "limit", defaultValue = "10") int size) {
+    public Response<CategoryVo> getCategoryPage(@PathVariable("categoryId") int categoryId,
+                                                @RequestParam(value = "page", defaultValue = "1") int page,
+                                                @RequestParam(value = "limit", defaultValue = "10") int size) {
 
 
         CategoryPageVo vo = goodsService.getGoodsAndBrotherCateById(categoryId, page, size);
         log.debug("通过分类浏览商品 : 商品={}", vo.getGoodsList());
 
-        return ResponseVO.ok(vo);
+        return Response.ok(vo);
     }
 
     @GetMapping("/goods/list/{categoryId}")
-    public ResponseVO<CategoryVo> getGoodsByCategory(@PathVariable("categoryId") int categoryId,
-                                                     @RequestParam(value = "page", defaultValue = "1") int page,
-                                                     @RequestParam(value = "limit", defaultValue = "10") int size) {
+    public Response<CategoryVo> getGoodsByCategory(@PathVariable("categoryId") int categoryId,
+                                                   @RequestParam(value = "page", defaultValue = "1") int page,
+                                                   @RequestParam(value = "limit", defaultValue = "10") int size) {
         CategoryPageVo vo = goodsService.getGoodsByCateId(categoryId, page, size);
         log.debug("通过分类浏览商品 : 商品={}", vo.getGoodsList());
-        return ResponseVO.ok(vo);
+        return Response.ok(vo);
 
     }
 
     @GetMapping("/goods/detail/{goodsId}")
-    public ResponseVO<GoodsDetailPageVo> getGoodsDetail(@PathVariable("goodsId") int goodsId) {
+    public Response<GoodsDetailPageVo> getGoodsDetail(@PathVariable("goodsId") int goodsId) {
         //更新浏览次数
         GoodsDetailVo goodsDetail = goodsService.getGoodsDetail(goodsId);
         List<GalleryVo> goodsGallery = goodsService.getGoodsGallery(goodsId);
@@ -67,15 +67,15 @@ public class GoodsController {
         GoodsDetailPageVo vo = new GoodsDetailPageVo(goodsDetail, goodsGallery);
         log.debug("浏览商品详情 : {}", vo);
 
-        return ResponseVO.ok(vo);
+        return Response.ok(vo);
     }
 
     @GetMapping("/goods/related/{goodsId}")
-    public ResponseVO<GoodsRelatedVo> getGoodsRelated(@PathVariable("goodsId") int goodsId) {
+    public Response<GoodsRelatedVo> getGoodsRelated(@PathVariable("goodsId") int goodsId) {
         GoodsRelatedVo vo = goodsService.getGoodsRelated(goodsId);
         log.debug("与 goodsId=[] 相关的商品 : {}", goodsId, vo);
 
-        return ResponseVO.ok(vo);
+        return Response.ok(vo);
     }
 
 

+ 7 - 8
goods-service/src/main/java/io/github/nnkwrik/goodsservice/controller/IndexController.java

@@ -2,7 +2,7 @@ package io.github.nnkwrik.goodsservice.controller;
 
 import io.github.nnkwrik.goodsservice.model.vo.CatalogVo;
 import io.github.nnkwrik.goodsservice.model.vo.IndexVO;
-import io.github.nnkwrik.goodsservice.model.vo.ResponseVO;
+import io.github.nnkwrik.common.dto.Response;
 import io.github.nnkwrik.goodsservice.service.IndexService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -22,30 +22,29 @@ public class IndexController {
     private IndexService indexService;
 
     @GetMapping("/index/index")
-    public ResponseVO<IndexVO> index() {
+    public Response<IndexVO> index() {
 
         IndexVO vo = indexService.getIndex();
         log.debug("浏览首页 : 广告 = {},分类 = {}, 商品 = {}", vo.getBanner(), vo.getChannel(), vo.getIndexGoodsList());
 
-        return ResponseVO.ok(vo);
+        return Response.ok(vo);
     }
 
     @GetMapping("/catalog/index")
-    public ResponseVO<CatalogVo> catalog() {
+    public Response<CatalogVo> catalog() {
 
         CatalogVo vo = indexService.getCatalogIndex();
         log.debug("浏览分类页 : 主分类 = {}, 展示子分类 = {}", vo.getCategoryList(), vo.getCurrentCategory());
 
-        return ResponseVO.ok(vo);
+        return Response.ok(vo);
     }
 
     @GetMapping("/catalog/{id}")
-    public ResponseVO<CatalogVo> subCatalog(@PathVariable("id") int id) {
+    public Response<CatalogVo> subCatalog(@PathVariable("id") int id) {
 
         CatalogVo vo = indexService.getCatalogById(id);
         log.debug("筛选分类 : 获取子分类 = {}", vo.getCurrentCategory());
 
-        return ResponseVO.ok(vo);
+        return Response.ok(vo);
     }
-
 }

+ 80 - 0
goods-service/src/main/java/io/github/nnkwrik/goodsservice/controller/SearchController.java

@@ -0,0 +1,80 @@
+package io.github.nnkwrik.goodsservice.controller;
+
+import io.github.nnkwrik.common.dto.Response;
+import io.github.nnkwrik.goodsservice.cache.SearchCache;
+import io.github.nnkwrik.goodsservice.model.vo.SearchIndexPageVo;
+import io.github.nnkwrik.goodsservice.model.vo.inner.GoodsSimpleVo;
+import io.github.nnkwrik.goodsservice.service.SearchService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * search相关的借口依赖openId,
+ *
+ * @author nnkwrik
+ * @date 18/11/18 21:17
+ */
+@Slf4j
+@RestController
+public class SearchController {
+
+
+    @Autowired
+    private SearchCache searchCache;
+
+
+    @Autowired
+    private SearchService searchService;
+
+
+    @PostMapping("/search/index")
+    public Response<SearchIndexPageVo> searchIndex(@RequestBody Map<String, String> map) {
+        String openId = map.get("openId");
+        List<String> historyKeyword = null;
+        if (!StringUtils.isEmpty(openId)) {
+            historyKeyword = searchService.getUserHistory(openId);
+        }
+
+        List<String> hotKeyword = searchCache.getHot(10);
+        SearchIndexPageVo vo = new SearchIndexPageVo(historyKeyword, hotKeyword);
+        log.info("用户openId= 【{}】获取搜索历史和热门关键词,搜索历史 = 【{}】,热门关键词 = 【{}】", openId, historyKeyword, hotKeyword);
+
+        return Response.ok(vo);
+    }
+
+    @PostMapping("/search/clearhistory")
+    public Response clearHistory(@RequestBody Map<String, String> map) {
+        String openId = map.get("openId");
+        if (StringUtils.isEmpty(openId)) return Response.fail(Response.OPEN_ID_IS_EMPTY, "用户id为空,请登陆后再尝试");
+        searchService.clearUserHistory(openId);
+        log.info("用户openId= 【{}】清空搜索历史", openId);
+        return Response.ok();
+    }
+
+
+    //TODO 后期提供排序和地区和卖家信用的筛选功能
+    @PostMapping("search/result/{keyword}")
+    public Response<List<GoodsSimpleVo>> searchGoods(@PathVariable("keyword") String keyword,
+                                                     @RequestParam(value = "page", defaultValue = "1") int page,
+                                                     @RequestParam(value = "limit", defaultValue = "10") int size,
+                                                     @RequestBody Map<String, String> map) {
+        String openId = map.get("openId");
+        List<GoodsSimpleVo> goodsListVo = searchService.searchByKeyword(keyword, page, size);
+
+        //数据库改openid的搜索历史
+        if (!StringUtils.isEmpty(openId)) {
+            searchService.updateUserHistory(openId, keyword);
+        }
+        //加入热门搜索缓存
+        searchCache.add(keyword.toLowerCase());
+        log.debug("用户 openid=【{}】,通过关键字【{}】搜索商品,搜索结果:{}", openId, keyword, goodsListVo);
+        return Response.ok(goodsListVo);
+    }
+
+
+}

+ 5 - 0
goods-service/src/main/java/io/github/nnkwrik/goodsservice/dao/GoodsMapper.java

@@ -5,6 +5,7 @@ import io.github.nnkwrik.goodsservice.model.po.GoodsGallery;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.Update;
 
 import java.util.List;
 
@@ -113,4 +114,8 @@ public interface GoodsMapper {
             "order by " + popular_score + " desc")
     List<Goods> findSimpleGoodsInSameParentCate(@Param("goodsId") int goodsId);
 
+    @Update("update goods set browse_count = browse_count + #{add} where id = #{goodsId}")
+    void addBrowseCount(@Param("goodsId") int id, @Param("add") int add);
+
+
 }

+ 1 - 2
goods-service/src/main/java/io/github/nnkwrik/goodsservice/dao/OtherMapper.java

@@ -2,6 +2,7 @@ package io.github.nnkwrik.goodsservice.dao;
 
 import io.github.nnkwrik.goodsservice.model.po.Ad;
 import io.github.nnkwrik.goodsservice.model.po.Channel;
+import io.github.nnkwrik.goodsservice.model.po.SearchHistory;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
@@ -26,6 +27,4 @@ public interface OtherMapper {
             "limit 5")
     List<Ad> findAd();
 
-    @Update("update goods set browse_count = browse_count + #{add} where id = #{goodsId}")
-    void addBrowseCount(@Param("goodsId") int id, @Param("add") int add);
 }

+ 60 - 0
goods-service/src/main/java/io/github/nnkwrik/goodsservice/dao/SearchMapper.java

@@ -0,0 +1,60 @@
+package io.github.nnkwrik.goodsservice.dao;
+
+import io.github.nnkwrik.goodsservice.model.po.Goods;
+import io.github.nnkwrik.goodsservice.model.po.SearchHistory;
+import org.apache.ibatis.annotations.*;
+
+import java.util.List;
+
+/**
+ * @author nnkwrik
+ * @date 18/11/20 20:18
+ */
+@Mapper
+public interface SearchMapper {
+
+    /**
+     * 热度算法
+     * Score = (1*click + 10 * want) /e^ (day/10)
+     * = (1*click + 10 * want) / e^((T(now) - T *  10^-7 )
+     */
+    String popular_score = "(1 * browse_count + 10 * want_count) / exp((now() - last_edit) * POW(10, -7))";
+
+
+    @Select("select keyword\n" +
+            "from search_history\n" +
+            "where user_id = #{user_id}\n" +
+            "order by search_time desc")
+    List<SearchHistory> findSearchHistory(@Param("user_id") String userId);
+
+
+    @Select("select id, `name`, primary_pic_url, price\n" +
+            "from goods\n" +
+            "where name like concat(concat('%',#{keyword}),'%') \n" +
+            "order by " + popular_score + " desc")
+    List<Goods> findGoodsByKeyword(@Param("keyword") String keyword);
+
+
+    @Delete("delete from search_history where user_id = #{user_id}")
+    void clearHistory(@Param("user_id") String userId);
+
+    @Delete("delete\n" +
+            "from search_history\n" +
+            "where id in (select id from (select id from search_history where user_id = #{user_id} order by search_time asc limit #{limit}) as tmp)")
+    void deleteOldHistory(@Param("user_id") String userId, @Param("limit") int limit);
+
+    @Select("SELECT EXISTS(SELECT 1 FROM search_history WHERE user_id = #{user_id}\n" +
+            "                                             and keyword = #{keyword})")
+    Boolean isExistedHistory(@Param("user_id") String userId, @Param("keyword") String keyword);
+
+    @Insert("insert into search_history (user_id, keyword) VALUES (#{user_id}, #{keyword})")
+    void insertHistory(@Param("user_id") String userId, @Param("keyword") String keyword);
+
+
+    @Update("update search_history\n" +
+            "set search_time = now()\n" +
+            "where user_id = #{user_id} and keyword = #{keyword};")
+    void updateSearchTime(@Param("user_id") String userId, @Param("keyword") String keyword);
+
+
+}

+ 15 - 0
goods-service/src/main/java/io/github/nnkwrik/goodsservice/model/po/SearchHistory.java

@@ -0,0 +1,15 @@
+package io.github.nnkwrik.goodsservice.model.po;
+
+import lombok.Data;
+
+/**
+ * @author nnkwrik
+ * @date 18/11/20 18:58
+ */
+@Data
+public class SearchHistory {
+    private Integer id;
+    private String userId;
+    private String keyword;
+    private Data searchTime;
+}

+ 19 - 0
goods-service/src/main/java/io/github/nnkwrik/goodsservice/model/vo/SearchIndexPageVo.java

@@ -0,0 +1,19 @@
+package io.github.nnkwrik.goodsservice.model.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * @author nnkwrik
+ * @date 18/11/18 21:14
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class SearchIndexPageVo {
+    private List<String> historyKeywordList;
+    private List<String> hotKeywordList;
+}

+ 20 - 0
goods-service/src/main/java/io/github/nnkwrik/goodsservice/service/SearchService.java

@@ -0,0 +1,20 @@
+package io.github.nnkwrik.goodsservice.service;
+
+import io.github.nnkwrik.goodsservice.model.vo.inner.GoodsSimpleVo;
+
+import java.util.List;
+
+/**
+ * @author nnkwrik
+ * @date 18/11/21 9:17
+ */
+public interface SearchService {
+
+    List<GoodsSimpleVo> searchByKeyword(String keyword, int page, int size);
+
+    List<String> getUserHistory(String openId);
+
+    void updateUserHistory(String openId, String keyword);
+
+    void clearUserHistory(String openId);
+}

+ 61 - 0
goods-service/src/main/java/io/github/nnkwrik/goodsservice/service/impl/SearchServiceImpl.java

@@ -0,0 +1,61 @@
+package io.github.nnkwrik.goodsservice.service.impl;
+
+import com.github.pagehelper.PageHelper;
+import io.github.nnkwrik.goodsservice.dao.SearchMapper;
+import io.github.nnkwrik.goodsservice.model.po.Goods;
+import io.github.nnkwrik.goodsservice.model.po.SearchHistory;
+import io.github.nnkwrik.goodsservice.model.vo.inner.GoodsSimpleVo;
+import io.github.nnkwrik.goodsservice.service.SearchService;
+import io.github.nnkwrik.goodsservice.util.PO2VO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author nnkwrik
+ * @date 18/11/21 9:22
+ */
+@Service
+public class SearchServiceImpl implements SearchService {
+
+    @Autowired
+    private SearchMapper searchMapper;
+
+
+    @Override
+    public List<GoodsSimpleVo> searchByKeyword(String keyword, int page, int size) {
+        PageHelper.startPage(page, size);
+        List<Goods> goodsList = searchMapper.findGoodsByKeyword(keyword);
+        return PO2VO.convertList(PO2VO.goodsSimple, goodsList);
+    }
+
+    @Override
+    public List<String> getUserHistory(String openId) {
+        List<SearchHistory> historyPo = searchMapper.findSearchHistory(openId);
+        int limit = 10;
+        if (historyPo.size() >= limit) {//或者超过一定数量就删除
+            searchMapper.deleteOldHistory(openId, historyPo.size() - limit);
+        }
+        return historyPo.stream()
+                .map(SearchHistory::getKeyword)
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    @Transactional
+    public void updateUserHistory(String openId, String keyword) {
+        if (searchMapper.isExistedHistory(openId, keyword)) {
+            searchMapper.updateSearchTime(openId, keyword);
+        } else {
+            searchMapper.insertHistory(openId, keyword);
+        }
+    }
+
+    @Override
+    public void clearUserHistory(String openId) {
+        searchMapper.clearHistory(openId);
+    }
+}

+ 2 - 0
goods-service/src/main/resources/application.yml

@@ -17,3 +17,5 @@ logging:
   level:
     org.springframework.web: info
     io.github.nnkwrik.goodsservice: debug
+jwt:
+  pub-key-file-name: RSA.pub