diff --git "a/\351\273\221\351\251\254\345\225\206\345\237\216\345\256\236\346\210\230\347\255\224\346\241\210.md" "b/\351\273\221\351\251\254\345\225\206\345\237\216\345\256\236\346\210\230\347\255\224\346\241\210.md"
index c8f4193d762e3a402652447b976eb9ea991e3943..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644
--- "a/\351\273\221\351\251\254\345\225\206\345\237\216\345\256\236\346\210\230\347\255\224\346\241\210.md"
+++ "b/\351\273\221\351\251\254\345\225\206\345\237\216\345\256\236\346\210\230\347\255\224\346\241\210.md"
@@ -1,1403 +0,0 @@
-# 黑马商城实战-上课笔记
-
-## 1.搭建运行环境
-
-### 1.1 前端配置
-
-```asciiarmor
-1、nginx不要放到有中文的目录
-2、前端项目不要放到有中文的目录
-3、查看错误日志:logs\error.log
-4、如果使用绝对路径,必须使用左斜杠: /
-C:/work/nginx-1.18.0-hmall/hm-mall-admin
-```
-
-```nginx
-server {
- listen 9001;
- server_name localhost;
- location / {
- root hm-mall-admin;
- }
-}
-
-server {
- listen 9002;
- server_name localhost;
- location / {
- root hm-mall-portal;
- }
-}
-```
-
-```sh
-#强制关闭nginx
-taskkill /f /im nginx.exe
-```
-
-管理端:http://localhost:9001
-
-用户端:http://localhost:9002
-
-
-
-### 1.2 网关配置
-
-#### 1.2.1.创建网关服务gateway
-
-```xml
-
-
-
- com.alibaba.cloud
- spring-cloud-starter-alibaba-nacos-discovery
-
-
-
- org.springframework.cloud
- spring-cloud-starter-gateway
-
-
-```
-新建application.yml:
-
-```yml
-server:
- port: 10010
-```
-
-
-
-#### 1.2.2.配置网关路由
-
-```yml
-spring:
- application:
- name: gateway
- cloud:
- gateway:
- routes:
- - id: userservice
- uri: lb://userservice # 路由的地址,lb,负载均衡
- predicates:
- - Path=/user/**,/address/**
- - id: orderservice
- uri: lb://orderservice
- predicates:
- - Path=/order/**,/pay/**
- - id: itemservice
- uri: lb://itemservice
- predicates:
- - Path=/item/**
- - id: searchservice
- uri: lb://searchservice
- predicates:
- - Path=/search/**
-```
-
-#### 1.2.3.在网关配置CORS
-
-```yml
- #注意缩进:和routes同级
- globalcors: # 全局的跨域处理
- add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
- corsConfigurations:
- '[/**]':
- allowedOrigins: # 允许哪些网站的跨域请求: *代表所有
- - "http://localhost:9001"
- - "http://localhost:9002"
- - "http://127.0.0.1:9001"
- - "http://127.0.0.1:9002"
- allowedMethods: # 允许的跨域ajax的请求方式
- - "GET"
- - "POST"
- - "DELETE"
- - "PUT"
- - "OPTIONS"
- allowedHeaders: "*" # 允许在请求中携带的头信息
- allowCredentials: true # 是否允许携带cookie
- maxAge: 360000 # 这次跨域检测的有效期
-```
-
-#### 1.2.4.启动网关服务
-
-编写启动类:com.hmall.gateway.GatewayApplication
-
-```java
-package com.hmall.gateway;
-
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-
-@SpringBootApplication
-public class GatewayApplication {
-
- public static void main(String[] args) {
- SpringApplication.run(GatewayApplication.class, args);
- }
-}
-```
-
-```asciiarmor
-1、必须启动nacos
-2、nacos不在本地的话,需要配置具体ip:
- nacos:
- server-addr: 虚拟机IP:8848
-```
-
-
-
-## 2.商品管理业务
-
-### 2.1.分页查询商品
-
-添加分页拦截器:
-
-```java
-package com.hmall.item.config;
-
-import com.baomidou.mybatisplus.annotation.DbType;
-import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
-import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-@Configuration
-public class MyBatisConfig {
- @Bean
- public MybatisPlusInterceptor mybatisPlusInterceptor() {
- MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
- //在sql之后追加limit
- interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
- return interceptor;
- }
-}
-```
-
-表现层ItemController中新增方法:
-
-```java
-@GetMapping("/list")
-public PageDTO- queryItemByPage(Integer page, Integer size) {
- // 分页查询
- Page
- result = itemService.page(new Page<>(page, size));
- // 封装并返回
- return new PageDTO<>(result.getTotal(), result.getRecords());
-}
-```
-
-### 2.2.根据id查询商品
-
-```java
-@GetMapping("{id}")
-public Item queryItemById(@PathVariable("id") Long id) {
- return itemService.getById(id);
-}
-```
-
-### 2.3.新增商品
-
-```java
-@PostMapping
-public void saveItem(@RequestBody Item item) {
- // 基本数据
- item.setCreateTime(new Date());
- item.setUpdateTime(new Date());
- item.setStatus(2); //默认下架
- // 新增
- itemService.save(item);
-}
-```
-
-### 2.4.商品上架、下架
-
-**业务层**
-
-```java
-void updateStatus(Long id, Integer status);
-```
-
-```java
-@Override
-public void updateStatus(Long id, Integer status) {
-
- //update tb_item set status = ? where id = ?
- this.update(Wrappers.
- lambdaUpdate()
- .set(Item::getStatus, status)
- .eq(Item::getId, id));
-
- //另外一种实现方式
- //this.lambdaUpdate()
- // .set(Item::getStatus, status)
- // .eq(Item::getId, id).update();
-}
-```
-
-**表现层**
-
-```java
-@PutMapping("/status/{id}/{status}")
-public void updateItemStatus(@PathVariable("id") Long id,
- @PathVariable("status") Integer status){
- itemService.updateStatus(id, status);
-}
-```
-
-### 2.5.修改商品
-
-```java
-@PutMapping
-public void updateItem(@RequestBody Item item) {
- // 基本数据
- item.setUpdateTime(new Date());
- // 不允许修改商品状态,所以强制设置为null,更新时,就会忽略该字段
- item.setStatus(null);
- // 更新
- itemService.updateById(item);
-}
-```
-
-### 2.6.根据id删除商品
-
-```java
-@DeleteMapping("{id}")
-public void deleteItemById(@PathVariable("id") Long id) {
- itemService.removeById(id);
-}
-```
-
-
-
-## 3.搜索业务
-
-### 3.1.创建搜索服务
-
-```asciiarmor
-注意:搜索服务不需要查询MySQL数据库,因此不能在pom添加任何MP和MySQL相关的坐标
-原因:SpringBoot自动配置:引入一个jar,自动给你产生一些对象(driver class, username, password)
-```
-
-```xml
-
-
-
- com.alibaba.cloud
- spring-cloud-starter-alibaba-nacos-discovery
-
-
-
- org.springframework.boot
- spring-boot-starter-web
-
-
-
- org.elasticsearch.client
- elasticsearch-rest-high-level-client
-
-
- com.alibaba
- fastjson
- 1.2.76
-
-
-
- org.springframework.boot
- spring-boot-starter-test
-
-
-
- com.hmall
- feign-api
- 1.0
-
-
-
-```
-
-```yml
-server:
- port: 8084
-spring:
- application:
- name: searchservice
- cloud:
- nacos:
- server-addr: localhost:8848
-```
-
-### 3.2.设计索引库数据结构
-
-基本字段包括:
-
-- 用于关键字全文检索的字段,比如All,里面包含name、brand、category信息
-- 分类
-- 品牌
-- 价格
-- 销量
-- id
-- name
-- 评价数量
-- 图片
-
-```json
-PUT /item
-{
- "mappings": {
- "properties": {
- "id":{
- "type": "keyword"
- },
- "name":{
- "type": "text",
- "analyzer": "ik_smart",
- "copy_to": "all"
- },
- "image":{
- "type": "keyword",
- "index": false
- },
- "price":{
- "type": "long"
- },
- "brand":{
- "type": "keyword",
- "copy_to": "all"
- },
- "category":{
- "type": "keyword",
- "copy_to": "all"
- },
- "sold":{
- "type": "integer"
- },
- "commentCount":{
- "type": "integer"
- },
- "isAD":{
- "type": "boolean"
- },
- "all":{
- "type": "text",
- "analyzer": "ik_smart"
- }
- }
- }
-}
-```
-
-
-
-ItemDoc类:
-
-```java
-package com.hmall.search.pojo;
-
-import com.hmall.common.dto.Item;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-import org.springframework.beans.BeanUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@Data
-@NoArgsConstructor
-public class ItemDoc {
- private Long id;
- private String name;
- private Long price;
- private String image;
- private String category;
- private String brand;
- private Integer sold;
- private Integer commentCount;
- private Boolean isAD;
-
- public ItemDoc(Item item) {
- // 属性拷贝
- BeanUtils.copyProperties(item, this);
- }
-}
-```
-
-### 3.3.数据导入
-
-要把数据库数据导入到elasticsearch中,包括下面几步:
-
-1)将商品微服务中的分页查询商品接口定义为一个远程调用的接口,放到**feign-api模块**中
-
-```java
-package com.hmall.common.client;
-
-import com.hmall.common.dto.Item;
-import com.hmall.common.dto.PageDTO;
-import org.springframework.cloud.openfeign.FeignClient;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.PutMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-
-@FeignClient(value = "itemservice")
-public interface ItemClient {
-
- //@RequestParam("page")不能省略,必须添加到参数前边(open-feign的要求)
- @GetMapping("/item/list")
- PageDTO
- queryItemByPage(@RequestParam("page") Integer page,
- @RequestParam("size") Integer size);
-
-}
-```
-
-在**搜索微服务**的引导类中:指定feign接口所在包
-
-```java
-package com.hmall.search;
-
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.cloud.openfeign.EnableFeignClients;
-
-@SpringBootApplication
-//==========注意导包
-@EnableFeignClients(basePackages = "com.hmall.common.client")
-public class SearchApplication {
- public static void main(String[] args) {
- SpringApplication.run(SearchApplication.class, args);
- }
-
- //指定es服务器的host和port
- @Bean
- public RestHighLevelClient restHighLevelClient() {
- return new RestHighLevelClient(RestClient.builder(
- HttpHost.create("http://192.168.200.130:9200")
- ));
- }
-
-}
-
-```
-
-2)**搜索服务**编写一个单元测试,实现下面功能:
-
-- 在**搜索服务中**需要使用Feign远程调用订单微服务,添加如下坐标:
-
- ```xml
-
-
- com.hmall
- feign-api
- 1.0
-
- ```
-
-- 调用item-service提供的FeignClient,分页查询商品 `PageDTO
- `
-
-- 将查询到的商品封装为一个`ItemDoc`对象,放入`ItemDoc`集合
-
-- 将`ItemDoc`集合批量导入elasticsearch中
-
-```asciiarmor
-因为要远程调用商品微服务,因此需要:
-1、启动Nacos
-2、启动itemservice
-```
-
-```java
-package com.hmall.search.feign;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.hmall.common.client.ItemClient;
-import com.hmall.common.dto.Item;
-import com.hmall.common.dto.PageDTO;
-import com.hmall.search.pojo.ItemDoc;
-import org.elasticsearch.action.bulk.BulkRequest;
-import org.elasticsearch.action.index.IndexRequest;
-import org.elasticsearch.client.RequestOptions;
-import org.elasticsearch.client.RestHighLevelClient;
-import org.elasticsearch.common.xcontent.XContentType;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-
-import java.io.IOException;
-import java.util.List;
-
-@SpringBootTest(classes = SearchApplication.class)
-public class FeignTest {
-
- @Autowired
- private ItemClient itemClient;
-
- @Autowired
- private RestHighLevelClient restHighLevelClient;
-
- @Test //注意导包,用长的那个:import org.junit.jupiter.api.Test;
- public void testQueryItem() throws IOException {
- // http://itemservice/item/list?page=1&size=5
- int page = 1, size = 100;
-
- while (true) {
- //远程调用商品微服务进行分页查询:
- PageDTO
- pageDTO = itemClient.queryItemByPage(page, size);
- List
- list = pageDTO.getList();
- if (CollectionUtils.isEmpty(list) || page > 10) {
- break;
- }
- // 1.准备BulkRequest
- BulkRequest request = new BulkRequest();
- // 2.准备DSL
- // 遍历
- for (Item item : list) {
- if(item.getStatus() == 2){
- // 下架商品直接跳过
- continue;
- }
- // 把 Item 转为 ItemDoc
- ItemDoc itemDoc = new ItemDoc(item);
- //将itemDoc使用jackson转成json数据
- String json = JSON.toJSONString(itemDoc);
- // 添加新增请求
- request.add(new IndexRequest("item")
- .id(itemDoc.getId().toString())
- .source(json, XContentType.JSON)
- );
- }
- // 3.发请求,批量处理
- restHighLevelClient.bulk(request, RequestOptions.DEFAULT);
- page++;
- }
- }
-}
-```
-
-
-
-### 3.4.实现基本搜索功能
-
-```json
-# 查询 分页 高亮 排序
-GET /item/_search
-{
- "query": {
- "match": {
- "all": "游戏"
- }
- },
- "from": 0,
- "size": 20,
- "sort": [
- {
- "price": {
- "order": "desc"
- }
- }
- ],
- "highlight": {
- "fields": {
- "name": {
- "require_field_match": "false",
- "pre_tags": [
- ""
- ],
- "post_tags": [
- ""
- ]
- }
- }
- }
-}
-```
-
-| 请求方式 | POST |
-| ---------- | ------------------------------------------------------------ |
-| 请求路径 | /search/list |
-| 请求参数 | RequestParams对象,
{
"key": "游戏手机",
"page": 2,
"size": 20,
"sortBy": "price",
"category": "手机",
"brand": "小米",
"minPrice": 1500,
"maxPrice": 999999
} |
-| 返回值类型 | `PageDTO`分页结果。
{ "total": 200, "list": [{}, {} , {}]} |
-| 接口说明 | 根据搜索条件搜索文档 |
-
-
-
-#### 1、根据接口定义方法
-
-根据接口文档中参数,定义实体类接收:
-
-```java
-package com.hmall.search.pojo;
-
-import lombok.Data;
-
-@Data
-//SearchDTO
-public class RequestParams {
- private String key;
- private Integer page = 1;
- private Integer size = 5;
- private String sortBy;
- private String brand;
- private String category;
- private Long minPrice;
- private Long maxPrice;
-}
-```
-
-SearchController:
-
-```java
-package com.hmall.search.web;
-
-@RestController
-@RequestMapping("/search")
-public class SearchController {
- @PostMapping("list")
- public PageDTO search(@RequestBody RequestParams params){
- //return searchService.search(params);
- return null;
- }
-}
-```
-
-#### 2、业务层实现
-
-> 接口:IService,实现类: Service
->
-> 接口:Service,实现类:ServiceImpl
-
-接口:
-
-```java
-public interface ISearchService {
- PageDTO search(RequestParams params);
-}
-```
-
-实现类:
-
-```java
-@Service
-public class SearchService implements ISearchService {
-
- @Autowired
- private RestHighLevelClient restHighLevelClient;
-
- @Override
- public PageDTO search(RequestParams params) {
- try {
- // 1.准备Request
- SearchRequest request = new SearchRequest("item");
-
- // 2.准备DSL
- // 2.1.query条件
- buildBasicQuery(request, params);
-
-
- // 2.2.分页
- int page = params.getPage();
- int size = params.getSize();
- request.source().from((page - 1) * size).size(size);
- // 2.3.排序
- String sortBy = params.getSortBy();
- if ("sold".equals(sortBy)) {
- request.source().sort(sortBy, SortOrder.DESC);
- } else if ("price".equals(sortBy)) {
- request.source().sort(sortBy, SortOrder.ASC);
- }
- // 2.4.高亮
- request.source().highlighter(new HighlightBuilder()
- .field("name")
- .requireFieldMatch(false)
- .preTags("")
- .postTags(""));
- // 3.发请求
- SearchResponse response =
- restHighLevelClient.search(request, RequestOptions.DEFAULT);
-
- // 4.解析结果
- SearchHits searchHits = response.getHits();
- // 4.1.total
- long total = searchHits.getTotalHits().value;
- // 4.2.数据
- SearchHit[] hits = searchHits.getHits();
- // 4.3.遍历
- List list = new ArrayList<>(hits.length);
- for (SearchHit hit : hits) {
- // 4.4.获取source
- String json = hit.getSourceAsString();
- // 4.5.转Java
- ItemDoc itemDoc = JSON.parseObject(json, ItemDoc.class);
- // 4.6.获取高亮
- Map map = hit.getHighlightFields();
- if (map != null && map.size() > 0) {
- HighlightField field = map.get("name");
- String value = field.getFragments()[0].string();
- itemDoc.setName(value);
- }
- list.add(itemDoc);
- }
- return new PageDTO<>(total, list);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-}
-```
-
-```java
-private void buildBasicQuery(SearchRequest request, RequestParams params) {
- // 1.创建布尔查询
- BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
- // 1.1. key
- String key = params.getKey();
- if (StringUtils.isNotBlank(key)) {
- // 非空
- boolQuery.must(QueryBuilders.matchQuery("all", key));
- } else {
- // 空
- boolQuery.must(QueryBuilders.matchAllQuery());
- }
-
- // 1.2. brand
- String brand = params.getBrand();
- if (StringUtils.isNotBlank(brand)) {
- //精确查询:keyword类型的字符串(不需要分词)
- boolQuery.filter(QueryBuilders.termQuery("brand", brand));
- }
- // 1.3. category
- String category = params.getCategory();
- if (StringUtils.isNotBlank(category)) {
- boolQuery.filter(QueryBuilders.termQuery("category", category));
- }
- // 1.4. price
- Long minPrice = params.getMinPrice()0;
- Long maxPrice = params.getMaxPrice();
-
- if (minPrice != null && maxPrice != null) {
- boolQuery.filter(
- QueryBuilders.rangeQuery("price")
- .gte(minPrice * 100) //MySQL和ES中记录的价格单位为分,前端传递的是元
- .lte(maxPrice * 100));
- }
-
- // 2.加入竞价排名(修改算法函数)
- FunctionScoreQueryBuilder queryBuilder = QueryBuilders.functionScoreQuery(
- boolQuery, //原始条件
- new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
- new FunctionScoreQueryBuilder.FilterFunctionBuilder(
- QueryBuilders.termQuery("isAD", true),
- ScoreFunctionBuilders.weightFactorFunction(100) //权重
- )
- }
- );
- //输出最终ES执行DSL语句
- System.out.println(queryBuilder);
- request.source().query(queryBuilder);
-}
-```
-
-#### 3、完善调用&测试
-
-```java
-@PostMapping("list")
-public PageDTO search(@RequestBody RequestParams params){
- return searchService.search(params);
-}
-```
-
-测试基本搜索:http://localhost:9002
-
-```asciiarmor
-注意:
-1、搜索服务启动后,要等成功注册到Nacos中才能在页面上测试查看
-2、测试竞价排名时,可以使用DSL将某些商品的isAD=true,再看效果
-POST /item/_update/626738
-{
- "doc": {
- "isAD": true
- }
-}
-```
-
-
-
-## 4.登录用户信息获取
-
-因为我们没有做登录功能,所以我们会默认用户已经登录。
-
-但是微服务运行中,需要获取一个登录的用户身份,该怎么办呢?
-
-
-
-### 4.1.给所有请求添加用户身份
-
-我们的要求是这样的:
-
-所有经过网关的请求,都在请求头中添加一个头信息:authorization = 2
-
-> 提示:在网关中配置默认过滤器,给header中添加authorization = 2
->
-> 提示:SpringCloud-day02(网关默认过滤器配置)
-
-gateway:
-
-```yaml
- default-filters: #注意层级,放到spring.cloud.gateway下
- - AddRequestHeader=authorization, 2
-```
-
-
-
-### 4.2.微服务获取用户身份
-
-网关已经给所有请求添加了用户身份,也就是authorization头信息。
-
-- 在**订单微服务**中编写一个SpringMVC的拦截器:HandlerInterceptor
-
- > 提示:SpringMVC-day02(拦截器)
-
- ```java
- package com.hmall.order.interceptors;
-
- import org.apache.commons.lang.StringUtils;
-
-
- @Slf4j
- public class UserInterceptor implements HandlerInterceptor {
- @Override
- public boolean preHandle(HttpServletRequest request,
- HttpServletResponse response,
- Object handler) throws Exception {
- //1、需要从请求header中获取authorization = 2
- //2、将userID放入ThreadLocal
- return true;
- }
-
- @Override
- public void afterCompletion(HttpServletRequest request,
- HttpServletResponse response,
- Object handler, Exception ex) throws Exception {
- // 用完不要忘了清理
- }
- }
- ```
-
- 指定拦截器拦截路径:
-
- ```java
- package com.hmall.order.config;
-
- import com.hmall.order.interceptors.UserInterceptor;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
- import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
-
- @Configuration
- public class MvcConfig implements WebMvcConfigurer {
-
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- registry.addInterceptor(new UserInterceptor()).addPathPatterns("/**");
- }
- }
- ```
-
-- 在拦截器中获取请求头中的authorization信息,也就是userId,并保存到ThreadLocal中
-
- > 提示:苍穹外卖-day02(新增员工时使用ThreadLocal)
- >
-
- ```java
- package com.hmall.order.utils;
-
- public class UserHolder {
-
- private static final ThreadLocal tl = new ThreadLocal<>();
-
- public static void setUser(Long userId) {
- tl.set(userId);
- }
- public static Long getUser() {
- return tl.get();
- }
- public static void removeUser(){
- tl.remove();
- }
- }
- ```
-
- ```java
- @Override
- public boolean preHandle(HttpServletRequest request,
- HttpServletResponse response,
- Object handler) throws Exception {
- // 1.获取请求头:authorization = 2
- String authorization = request.getHeader("authorization");
- if(StringUtils.isBlank(authorization)){
- log.warn("非法用户访问!请求路径:{}", request.getRequestURI() );
- // 没有用户信息,未登录 403 禁止
- response.setStatus(403);
- return false; //返回false,直接结束请求(不再到达Controller)
- }
- // 2.转换用户id
- Long userId = Long.valueOf(authorization);
- // 3.存入ThreadLocal
- UserHolder.setUser(userId);
- // 3.放行
- return true;
- }
-
- @Override
- public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
- // 用完不要忘了清理
- UserHolder.removeUser();
- }
- ```
-
-
-### 4.3.测试
-
-在订单服务中,获取用户ID:OrderController
-
-```java
-@GetMapping("/hi")
-public String hi() {
- Long userId = UserHolder.getUser();
- System.err.println(userId);
- return "hi";
-}
-```
-
-http://localhost:10010/order/hi
-
-```asciiarmor
-启动网关服务,订单服务即可
-```
-
-
-
-## 5.用户相关业务
-
-### 5.1.根据用户id查询地址列表
-
-接口说明:
-
-| 请求方式 | GET |
-| ---------- | ------------------------------------------------------------ |
-| 请求路径 | /address/uid/{userId} |
-| 请求参数 | userId:用户id |
-| 返回值类型 | `List`
[
{
"id":61,
"userId":2,
"contact":"李佳星",
"mobile":"13301212233",
"province":"上海",
"city":"上海",
"town":"浦东新区",
"street":"航头镇航头路",
"isDefault":true
}
] |
-| 接口说明 | 根据用户id查询地址列表 |
-
-AddressController
-
-```java
-@GetMapping("/uid/{userId}")
-public List findAddressByUserId(@PathVariable("userId") Long userId) {
-
- //where user_id =?
- return addressService.list(
- //new LambdaQueryWrapper().eq(Address::getUserId, userId)
- Wrappers.lambdaQuery().eq(Address::getUserId, userId)
- );
- //return addressService.lambdaQuery().eq(Address::getUserId, userId).list();
-}
-```
-
-### 5.2.根据addressId查询Address
-
-接口说明:
-
-| 请求方式 | GET |
-| ---------- | ------------------------------------------------------------ |
-| 请求路径 | /address/{addressId} |
-| 请求参数 | addressId:地址id |
-| 返回值类型 | Adderss对象:
{
"id":61,
"userId":2,
"contact":"李佳星",
"mobile":"13301212233",
"province":"上海",
"city":"上海",
"town":"浦东新区",
"street":"航头镇航头路",
"isDefault":true
} |
-| 接口说明 | 根据addressId查询地址。 |
-
-```java
-@GetMapping("/{addressId}")
-public Address findAddressById(@PathVariable("addressId") Long addressId) {
- return addressService.getById(addressId);
-}
-```
-
-
-
-## 6.下单业务
-
-```
-MyBatisPlus提供代码生成器生成:实体类、数据层、业务层、表现层
-```
-
-### 6.1 保存订单
-
-| 请求方式 | POST |
-| ---------- | ------------------------------------------------------------ |
-| 请求路径 | /order |
-| 请求参数 | {
"num": 1, # 代表购买数量
"paymentType": 3, # 代表付款方式
"addressId": 61, # 代表收货人地址id
"itemId": 100000003145 # 代表商品id
} |
-| 返回值类型 | Order,订单对象 |
-| 接口说明 | 创建订单 |
-
-创建订单业务比较复杂,流程如下:
-
-- 1)根据雪花算法生成订单id
-- 2)商品微服务提供FeignClient,实现根据id查询商品的接口
-- 3)根据itemId查询商品信息
-- 4)基于商品价格、购买数量计算商品总价:totalFee
-- 5)封装Order对象,初识status为未支付
-- 6)将Order写入数据库tb_order表中
-- 7)将商品信息、orderId信息封装为OrderDetail对象,写入tb_order_detail表
-- 8)将user-service的根据id查询地址接口封装为FeignClient
-- 9)根据addressId查询user-service服务,获取地址信息
-- 10)将地址封装为OrderLogistics对象,写入tb_order_logistics表
-- 11)在item-service提供减库存接口,并编写FeignClient
-- 12)调用item-service的减库存接口
-
-#### 1、根据接口定义方法
-
-在订单服务中定义接受参数类:
-
-```java
-package com.hmall.order.pojo;
-
-import lombok.Data;
-
-@Data
-//OrderDTO
-public class RequestParams {
- private Integer num; //购买数量
- private Long itemId; //商品ID
- private Long addressId;//配送地址
- private Integer paymentType; //支付方式
-}
-```
-
-```java
- @PostMapping
- public Order createOrder(@RequestBody RequestParams requestParams){
- //return orderService.createOrder(requestParams);
-
- //返回订单ID
- return null;
- }
-```
-
-#### 2、业务层实现
-
-创建订单业务比较复杂,流程如下:
-
-- 1)根据雪花算法生成订单id
-- 2)商品微服务提供FeignClient,实现根据id查询商品的接口
-- 3)根据itemId查询商品信息
-- 4)基于商品价格、购买数量计算商品总价:totalFee
-- 5)封装Order对象,初识status为未支付
-- 6)将Order写入数据库tb_order表中
-- 7)将商品信息、orderId信息封装为OrderDetail对象,写入tb_order_detail表
-- 8)将user-service的根据id查询地址接口封装为FeignClient
-- 9)根据addressId查询user-service服务,获取地址信息
-- 10)将地址封装为OrderLogistics对象,写入tb_order_logistics表
-- 11)在item-service提供减库存接口,并编写FeignClient
-- 12)调用item-service的减库存接口
-
-要操作三张表,先把数据层创建出来:
-
-```java
-package com.hmall.order.mapper;
-
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.hmall.order.pojo.OrderDetail;
-
-public interface OrderDetailMapper extends BaseMapper {
-
-}
-```
-
-```java
-package com.hmall.order.mapper;
-
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.hmall.order.pojo.OrderLogistics;
-
-public interface OrderLogisticsMapper extends BaseMapper {
-
-}
-```
-
-
-
-在业务层新增:createOrder方法
-
-```java
-package com.hmall.order.service;
-
-import com.baomidou.mybatisplus.extension.service.IService;
-import com.hmall.order.pojo.Order;
-import com.hmall.order.pojo.RequestParams;
-
-public interface IOrderService extends IService {
-
- Order createOrder(RequestParams requestParams);
-
-}
-```
-
-
-
-##### 2.1 指定雪花算法ID
-
-> 1、MyBatisPlus提供:@TableId(type = IdType.ASSIGN_ID)
->
-> 2、雪花算法生成的ID比较长,返回给前端后精度会丢失,因此转换成字符串再返回
-
-```java
-@Data
-@TableName("tb_order")
-public class Order{
- /**
- * 订单编号, 自动生成雪花算法ID
- */
- @TableId(type = IdType.ASSIGN_ID)
- @JsonSerialize(using = ToStringSerializer.class)
- private Long id;
-
- //...
-}
-```
-
-##### 2.2 计算订单价格
-
-> 前端传递的只有商品ID,因此需要远程调用商品服务查询商品详情
-
-1、在feign-api中添加远程调用接口:
-
-```java
-package com.hmall.common.client;
-
-import com.hmall.common.dto.Item;
-import com.hmall.common.dto.PageDTO;
-import org.springframework.cloud.openfeign.FeignClient;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.PutMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-
-@FeignClient(value = "itemservice")
-public interface ItemClient {
-
- @GetMapping("/item/list")
- PageDTO- queryItemByPage(@RequestParam("page") Integer page,
- @RequestParam("size") Integer size);
-
- @GetMapping("/item/{id}")
- Item queryItemById(@PathVariable("id") Long id);
-
-}
-```
-
-需要扫描到上述接口所在包:
-
-```java
-@MapperScan("com.hmall.order.mapper")
-@SpringBootApplication
-@EnableFeignClients(basePackages = "com.hmall.common.client")
-public class OrderApplication {
-
- public static void main(String[] args) {
- SpringApplication.run(OrderApplication.class, args);
- }
-
-}
-```
-
-2、完善OrderService中的下单方法
-
-```java
-package com.hmall.order.service.impl;
-
-@Service
-public class OrderService extends ServiceImpl implements IOrderService {
-
- @Autowired
- private ItemClient itemClient;
- @Autowired
- private UserClient userClient;
-
- @Autowired
- private OrderDetailMapper orderDetailMapper;
-
- @Autowired
- private OrderLogisticsMapper orderLogisticsMapper;
-
- @Transactional
- public Order createOrder(RequestParams requestParams) {
-
- // 1.查询商品:远程调用商品服务根据ID查询商品详情
- Item item = itemClient.queryItemById(requestParams.getItemId());
- // 2.基于商品价格 * 购买数量计算商品总价:totalFee
- long totalFee = item.getPrice() * requestParams.getNum();
-
- return null;
- }
-}
-```
-
-##### 2.3 保存订单信息
-
-```java
-@Transactional
-public Order createOrder(RequestParams requestParams) {
-
- // 1.查询商品:远程调用商品服务根据ID查询商品详情
- Item item = itemClient.queryItemById(requestParams.getItemId());
- // 2.基于商品价格 * 购买数量计算商品总价:totalFee
- long totalFee = item.getPrice() * requestParams.getNum();
-
-
- //=====================新增==========================
- Order order = new Order();
- order.setTotalFee(totalFee); //总金额
- order.setPaymentType(requestParams.getPaymentType());
- order.setUserId(UserHolder.getUser()); //获取当前用户ID
- order.setStatus(1); //设置状态为:未支付
- // 3.将Order写入数据库tb_order表中
- this.save(order); //保存成功后,框架底层:order.setId(xxx)
- //获取订单id:order.getId();
-
- // 4.将商品信息、orderId信息封装为OrderDetail对象,写入tb_order_detail表
- OrderDetail detail = new OrderDetail();
- //detail.setName(item.getName());
- //detail.setSpec(item.getSpec());
- //detail.setPrice(item.getPrice());
- //detail.setImage(item.getImage());
- //属性名一致,可以使用对象拷贝
- BeanUtils.copyProperties(item, detail);
- //下面几个属性名不一致,必须使用set方法赋值
- detail.setOrderId(order.getId());
- detail.setItemId(item.getId());
- detail.setNum(requestParams.getNum());
- orderDetailMapper.insert(detail);
-
- return order;
-}
-```
-
-
-
-##### 2.4 保存订单配送信息
-
-> 前端传递的只有地址ID,因此需要远程调用用户服务查询地址详情
-
-1、在feign-api模块中创建远程调用接口:
-
-```java
-package com.hmall.common.client;
-
-import com.hmall.common.dto.Address;
-import org.springframework.cloud.openfeign.FeignClient;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-
-@FeignClient(value = "userservice")
-public interface UserClient {
- @GetMapping("/address/{id}")
- Address findAddressById(@PathVariable("id") Long id);
-}
-```
-
-2、完善OrderService中的下单方法
-
-```java
-//====================新增================
-// 5.远程调用:根据addressId查询user-service服务,获取地址信息
-Address address = userClient.findAddressById(requestParams.getAddressId());
-// 6.将地址封装为OrderLogistics对象,写入tb_order_logistics表
-OrderLogistics orderLogistics = new OrderLogistics();
-BeanUtils.copyProperties(address, orderLogistics);
-//两个属性名称不一致:order.id, order_detai.orderId,必须自己手动设置
-orderLogistics.setOrderId(order.getId());
-orderLogisticsMapper.insert(orderLogistics);
-```
-
-
-
-##### 2.5 扣减库存
-
-> 商品库存保存在商品表,因此需要远程调用**商品服务进行扣减**
-
-1、在**商品服务**中添加扣减库存实现
-
-在**表现层**ItemController定义接口:
-
-```java
-@PutMapping("/stock/{itemId}/{num}")
-public void updateStock(@PathVariable("itemId") Long itemId,
- @PathVariable("num") Integer num) {
- itemService.deductStock(itemId, num);
-}
-```
-
-**业务层**:IItemService
-
-```java
-//接口:IItemService
-void deductStock(Long itemId, Integer num);
-```
-
-```java
-//实现类:ItemService
-@Autowired
-private ItemMapper itemMapper;
-
-@Override
-public void deductStock(Long itemId, Integer num) {
- try {
- itemMapper.updateStock(itemId, num);
- } catch (Exception e) {
- throw new RuntimeException("库存不足!");
- }
-}
-```
-
-**数据层**ItemMapper:
-
-```java
-@Update("update tb_item set stock = stock + #{num} where id = #{itemId}")
-void updateStock(@Param("itemId") Long itemId, @Param("num") Integer num);
-```
-
-
-
-2、在feign-api的ItemClient中添加远程调用接口:
-
-```java
-//ItemClient
-@PutMapping("/item/stock/{itemId}/{num}")
-void updateStock(@PathVariable("itemId") Long itemId, @PathVariable("num") Integer num);
-```
-
-
-
-3、完善OrderService中的下单方法
-
-```java
-// 7.远程调用:扣减库存
-try {
- //注意:传递负数,扣减库存
- itemClient.updateStock(requestParams.getItemId(), -requestParams.getNum());
-} catch (Exception e) {
- throw new RuntimeException("库存不足!");
-}
-```
-
-#### 3、完善调用&测试
-
-```java
-@PostMapping
-public Order createOrder(@RequestBody RequestParams requestParams){
- return orderService.createOrder(requestParams);
-}
-```
-
-测试下单:http://localhost:9002
-
-```asciiarmor
-必须启动所有的微服务
-```