外卖点餐系统06

外卖点餐系统06

HttpClient

HttpClient 是 Java 中一个用于发送 HTTP 请求和接收 HTTP 响应的类库,最常见于与 Web 服务进行交互。
要在Java中使用HttpClient,只需要在Maven中导入HttpClien坐标:

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.13</version>
</dependency>

HttpClient核心Api

  1. HttpClient:
  • HttpClient 是 Java 11 中引入的一个类,用于发送 HTTP 请求和处理 HTTP 响应。它支持同步和异步请求。
  • 主要用于:发起 HTTP 请求,并接收来自服务器的响应。
  1. HttpClients:
  • HttpClients 是 Apache HttpComponents 提供的工具类,用于创建 CloseableHttpClient 对象。它为 Java 8 和更早版本的开发者提供了一个类似的 HTTP 客户端。
  • 主要用于:管理 CloseableHttpClient 对象的创建。
  1. CloseableHttpClient:
  • CloseableHttpClient 是 Apache HttpClient 中的类,提供了一个 HTTP 客户端的实现,它是可关闭的,且常用于通过 execute 方法发送请求。
  • 主要用于:发送 HTTP 请求并接收响应。
  1. HttpGet:
  • HttpGet 是 Apache HttpClient 中的类,用于执行 HTTP GET 请求,常用于从服务器获取数据。
  • 主要用于:向服务器请求数据。
  1. HttpPost:
  • HttpPost 是 Apache HttpClient 中的类,用于执行 HTTP POST 请求,常用于向服务器发送数据(如表单提交、文件上传等)。
  • 主要用于:向服务器发送数据。

发送请求步骤

  • 创建HttpClient对象
  • 创建Http请求对象
  • 调用HttpClient的execute方法发送请求
  • 解析返回结果
  • 关闭资源
    通过HttpClient发送Get方式请求
    /**
     * 测试通过Httpclient发送Get方式请求
     */
    @Test
    public void testGet() throws Exception {
        // 创建HttpClient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();
        // 创建Http请求对象
        HttpGet httpGet = new HttpGet("http://localhost:8080/user/shop/status");

        // 调用HttpClient的execute方法发送请求, 接收响应结果
        CloseableHttpResponse response = httpClient.execute(httpGet);

        // 获取服务端返回的状态码,解析服务端返回的数据
        int statusCode = response.getStatusLine().getStatusCode();
        System.out.println(statusCode);

        HttpEntity entity = response.getEntity();
        String body = EntityUtils.toString(entity);
        System.out.println("服务端返回的数据为:" + body);
        
        // 关闭资源
        response.close();
        httpClient.close();
    }

通过HttpClient发送Post方式请求,Post方式与Get方式类似,但是Post需要传递参数,中间部分代码都是为了设置Post请求体

/**
     * 测试通过Httpclient发送Get方式请求
     */
    @Test
    public void testPost() throws Exception {
        // 创建HttpClient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();
        // 下面的代码是构造一个请求,请求内容是json形式,用提供的类创造json请求
        // 创建请求对象
        HttpPost httpPost = new HttpPost("http://localhost:8080/admin/employee/login");
        // Post 方式要提交请求参数,通过请求体以json方式提交
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("username", "admin");
        jsonObject.put("password", "123456");
        StringEntity entity = new StringEntity(jsonObject.toString());
        // 指定请求编码方式
        entity.setContentEncoding("UTF-8");
        // 指定数据格式
        entity.setContentType("application/json");
        httpPost.setEntity(entity);

        // 发送请求
        CloseableHttpResponse response = httpClient.execute(httpPost);

        // 解析返回结果
        int statusCode = response.getStatusLine().getStatusCode();
        System.out.println("响应码为:" + statusCode);

        HttpEntity entity1 = response.getEntity();
        String body = EntityUtils.toString(entity1);
        System.out.println("服务端返回的数据为:" + body);

        // 关闭资源
        response.close();
        httpClient.close();
    }

微信小程序开发

开发微信小程序之前需要做如下准备工作:

  • 注册小程序
  • 完善小程序信息
  • 下载开发者工具
  1. 注册小程序
    注册地址:https://mp.weixin.qq.com/wxopen/waregister?action=step1
  2. 完善小程序信息
    完善小程序信息、小程序类目
    完善小程序信息和小程序类目.jpg
    记得保存微信小程序的AppID和密钥
    查看微信小程序的AppID.jpg
  3. 下载开发者工具
    下载地址: https://developers.weixin.qq.com/miniprogram/dev/devtools/stable.html
    下载开发者工具.jpg
    微信开发者工具界面
    微信开发者工具界面.jpg
    由于微信小程序开发主要是前端代码,所以我们直接导入提供的前端代码, 并需要将不校验合法域名勾选
    导入前端代码后的微信开发小程序.jpg

微信登陆流程

微信小程序开发提供了微信登陆的官方文档,我们需要根据这个官方文档来开发微信登陆功能
微信官方文档: https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html
微信登陆流程时序.jpg

后端开发步骤

  1. 在小程序调用wx.login()获得code,这个code是授权码,然后将这个code通过wx.request()发送给Developer Service,也就是我们编写的后端服务
  2. 后端拿到了授权码code后,通过Httpclient调用Wechat Http Api,调用这个微信接口服务时,需要传参数(appid,appsecret,code),微信接口服务通过Httpclient返回session_key和openid,这个openid就是微信用户的唯一标识
  3. 后端获取到openid之后,后端可以通过这个openid自定义操作,譬如产生token记录当前用户,并将这个自定义登陆状态,返回给小程序
  4. 小程序通过后端返回的自定义登陆状态发起业务请求(业务请求中包含自定义登陆状态),后端收到业务请求后,会根据自定义登陆状态进行业务操作返回相应业务数据

总结

  1. 调用 wx.login() 获取 临时登录凭证code ,并回传到开发者服务器。
  2. 调用 auth.code2Session 接口,换取 用户唯一标识 OpenID 、 用户在微信开放平台账号下的唯一标识UnionID(若当前小程序已绑定到微信开放平台账号) 和 会话密钥 session_key。
    之后开发者服务器可以根据用户标识来生成自定义登录态,用于后续业务逻辑中前后端交互时识别用户身份。

代码开发

增加相关配置项

首先需要配置微信登陆配置项,包括

  • 调用微信接口服务接口所需要的Appid和AppSecret
  • 为微信用户生成jwt令牌时使用的配置项,管理端和用户端令牌分开配置
sky:
  jwt:
     #用户端令牌
     user-secret-key: itheima
     user-ttl: 7200000
     user-token-name: authentication   # 这个名称是与前端约定好的
  wechat: 
     appid: ${sky.wechat.appid}
     secret: ${sky.wechat.secret}

需求设计和具体代码实现

微信小程序登陆接口需求设计.png
通过接口设计我们知道,这是一个Post请求方式,传递数据使用UserLoginDTO,该DTO只有一个code变量,对应接口设计中的请求体body中的code,返回数据封装成UserLoginVO,与接口设计的返回数据中的data变量一一对应。
后端根据前端传过来的code调用微信接口服务获取到当前登陆用户的信息,然后后端会根据用户信息生成jwt令牌,然后后续该用户的操作都会绑定该令牌,后端通过校验该令牌来确定前端请求的操作是合法的,校验令牌的操作是通过拦截器来实现的。

    /**
     * 微信登陆
     * @param userLoginDTO
     * @return
     */
    @PostMapping("/login")
    @ApiOperation("微信登陆")
    public Result<UserLoginVO> login(@RequestBody UserLoginDTO userLoginDTO) {  // @RequestBody 是将JSON对象反序列化为java对象
        log.info("微信用户登陆:{}", userLoginDTO.getCode());

        // 微信登陆
        User user = userService.wxLogin(userLoginDTO);

        // 为微信用户生成jwt令牌,调用工具类JwtUtil
        Map<String, Object> map = new HashMap<>();
        // 用户的唯一标识user.getId()
        map.put(JwtClaimsConstant.USER_ID, user.getId());
        String token = JwtUtil.createJWT(jwtProperties.getUserSecretKey(), jwtProperties.getUserTtl(), map);

        // 最终需要返回VO,所以我们还需要封装VO
        UserLoginVO userLoginVO = UserLoginVO.builder()
                .id(user.getId())
                .openid(user.getOpenid())
                .token(token)
                .build();
        return Result.success(userLoginVO);

    }

我们接收到前端传来的code,在后端我们需要通过Httpclient访问Wechat Http Api来获取session_key和Openid,这部分代码逻辑我们在userService.java中完成,并将获得的session_key和openid存储在实体User中。
在userServiceImpl.java中的wxlogin方法中,主要完成了通过HttpClient访问微信接口服务,并获得了当前登陆用户的openId,该openid是登陆用户的唯一标识,然后根据该openid在User表中查询当前用户的相关数据,并返回给UserController.java中的login方法,然后传递到前端小程序。具体代码开发看下面的代码块:
userServiceImpl.java

public class UserServiceImpl implements UserService {

   public static final String WX_LOGIN = "https://api.weixin.qq.com/sns/jscode2session";

   @Autowired
   private WeChatProperties weChatProperties;      // 微信小程序的配置类,通过这个配置类读取微信小程序的AppID和Secret

   @Autowired
   private UserMapper userMapper;
   /**
    * 微信登陆
    * @param userLoginDTO
    * @return
    */
   public User wxLogin(UserLoginDTO userLoginDTO) {
      // 通过HttpClient调用微信接口服务,获取当前微信用户的openid,项目中有封装好了的HttpClient工具类,HttpClientUtil.java
      // 通过配置属性类读出 配置好的微信小程序的appid和secret值
      Map<String, String> map = new HashMap<>();
      map.put("appid", weChatProperties.getAppid());
      map.put("secret", weChatProperties.getSecret());
      map.put("js_code", userLoginDTO.getCode());
      map.put("grant_type", "authorization_code");
      String json = HttpClientUtil.doGet(WX_LOGIN, map);

      // 微信接口服务返回的是JSON类型的数据格式,我们需要将JSON格式转成Java对象,然后通过getter方法取出对象的属性
      JSONObject jsonObject = JSONObject.parseObject(json);
      String openid = jsonObject.getString("openid");

      // 判断openid是否为空,如果空表示登陆失败,跑出业务异常
      if(openid==null){
         throw new LoginFailedException(MessageConstant.LOGIN_FAILED);
      }

      // 我们需要判断当前用户是否为新用户,也就是openid是否在我们的User表中,需要使用UserMapper.java
      // 如果不为空,说明当前微信用户的openid存在,不为新用户
      // 如果是新用户,自动完成注册,注册需要创建一个user对象,并存储在User表中
      User user = userMapper.getByOpenId(openid);
      if(user==null){ // 新用户,完成注册
         user = User.builder()
                 .openid(openid)
                 .createTime(LocalDateTime.now())
                 .build();
         userMapper.insert(user);        // 这里的插入操作,需要返回user表的id,因为在userController中使用到了id,
         // 所以在mapper层编写MyBatis需要使用useGeneratedKeys="true" keyProperty="id
      }
      // 返回用户对象
      return user;
   }
}

导入商品浏览功能代码

这部分代码,与第四天的代码差别不大,在导入好代码之后,如果出现401报错,大概率是拦截器没有设置好,需要将拦截器中的admin全部换成user,检查一遍一般就能发现问题。
微信小程序用户端商品浏览代码.png

Read more

如何设计秒杀系统

什么是秒杀设计 秒杀设计 是一种针对高并发、大流量场景的系统设计,通常应用于电商活动中(如限时抢购、促销等),用户在非常短的时间内大量涌入系统,抢购有限的商品或优惠。这种场景下,系统需要能够承受巨大的瞬时并发请求,同时保证数据的一致性和业务的正确性。 秒杀技术分析 秒杀系统贯穿活动、商品和下单三个领域,涵盖了页面静态化、接口限流、Redis预减库存、异步下单和接口动态化等技术。 需要迎接的挑战有: 1. 高并发和压力测试:秒杀活动会带来巨大的流量,服务器和数据库的并发处理能力是关键。 2. 保证数据一致性:抢购涉及到商品库存的实时减少,需要保证在高并发场景下库存数据的准确性和一致性 3. 防止超卖和重复购买:确保同一商品不会被重复购买,同时避免超卖,即使是在极端的高并发情况下 4. 分布式锁和限流:使用分布式锁来保护关键资源,限制用户访问频率以免系统崩溃 5. 性能优化:包括代码层面的优化、数据库的优化、缓存的使用等,以提高系统性能和响应速度。 页面静态化 秒杀页面静态化是将动态生成的秒杀页面转换为静态HTML页面,从而提高页面响应速度和系统性能

By Yucan Huang

分布式系统下雪花算法生成全局唯一ID

雪花算法 在分布式系统中,为了避免多个节点生成相同的唯一标识符id,我们通常使用一种全局唯一ID生成策略,而Snowflake Algorithm就是广泛使用的解决方案之一。 雪花算法简介 雪花算法生成的 ID 是一个 64 位的二进制整数,具有以下组成部分: 部分 字节长度 描述 符号位 1bit 固定为0,因为生成的ID是整数 时间戳 41bits 表示时间戳,单位是毫秒,可以存储大约68年的时间 机器ID 10bits 用于标识不同的机器(节点)。10位可以将其分数据中心ID(5位)和机器ID(5位),共支持1024台机器同时生成ID 序列号 12bits 同一毫秒内的序列号,用来区分在同一台机器、同一时间戳下生成的多个ID,最大支持同一毫秒生成4096个唯一ID 代码实现 package com.can.springbootmessage; public class SnowflakeIdGenerator { // 起始时间戳,Long是64位,

By Yucan Huang

责任链设计模式

什么是责任链模式 项目有个请求,需要有对应的服务来处理,然后这个请求可能需要被很多个层级权限的服务来处理。我们将这些处理该请求的服务放在一条链上,链从前往后,是层级更高的服务,第一个服务处理不了,传递到链上的下一个服务,直到这个请求被处理成功。 责任链模式(Chain of Responsibility)是一种处理请求的模式,它让多个处理器都有机会处理该请求,直到其中某个处理器成功处理该请求,责任链模式把多个处理器串成链,然后让请求在链上传递。 1. 如何把多个处理器串成链,然后让请求在链上传递 客户端发送请求,处理类去处理它的请求,所以有个处理方法(handleRequest),处理类要连接在一起,所以**处理类要有一个方法(成员变量nextHandler)**指向下一个处理类。 我们抽象出一个公共的父类,然后去定义不同的处理类,这些处理类通过nextHandler连接起来。 2. 代码演示 package interview.pattern; public class ChainRespPattern { Handl

By Yucan Huang