课程内容

  • HttpClient:在java中构造请求,发送请

    • 向微信发送请求,比如登录请求

  • 微信小程序开发

  • 微信登录

  • 导入商品浏览功能代码

功能实现:微信登录商品浏览

微信登录效果图:

商品浏览效果图:

1. HttpClient

1.1 介绍

HttpClient作用:

  • 发送HTTP请求

  • 接收响应数据

HttpClient 是Apache Jakarta Common 下的子项目,可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。

为什么要在Java程序中发送Http请求?有哪些应用场景呢?

HttpClient应用场景:

当我们在使用扫描支付、查看地图、获取验证码、查看天气等功能时

其实,应用程序本身并未实现这些功能,都是在应用程序里访问提供这些功能的服务,访问这些服务需要发送HTTP请求,并且接收响应数据,可通过HttpClient来实现。

HttpClient的maven坐标:

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

HttpClient的核心API:

  • HttpClient:Http客户端对象类型,使用该类型对象可发起Http请求。

  • HttpClients:可认为是构建器,可创建HttpClient对象。

  • CloseableHttpClient:实现类,实现了HttpClient接口。

  • HttpGet:Get方式请求类型。

  • HttpPost:Post方式请求类型。

HttpClient发送请求步骤:

  • 创建HttpClient对象

  • 创建Http请求对象

  • 调用HttpClient的execute方法发送请求

1.2 入门案例

对HttpClient编程工具包有了一定了解后,那么,我们使用HttpClient在Java程序当中来构造Http的请求,并且把请求发送出去,接下来,就通过入门案例分别发送GET请求POST请求,具体来学习一下它的使用方法。

正常来说,首先,应该导入HttpClient相关的坐标,但在项目中,就算不导入,也可以使用相关的API。

因为在项目中已经引入了aliyun-sdk-oss坐标:

<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
</dependency>

上述依赖的底层已经包含了HttpClient相关依赖。

1.2.1 GET方式请求

实现步骤:

  1. 创建HttpClient对象

  2. 创建请求对象,设置url

  3. 发送请求,接受响应结果

  4. 解析结果

    1. 状态码

    2. 响应体:getEntity接收响应体,EntityUtils解析响应体获得body数据

  5. 关闭资源

    1. 关闭响应

    2. 关闭HttpClient

    /**
     * 测试通过HttpClient发送GET方式请求
     */
    @Test
    public void testGet() throws IOException {
        //1.创建HttpClient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();

        //2.创建HTTP请求对象
        HttpGet httpGet = new HttpGet("http://localhost:8090/user/shop/status");

        //3.发送请求,并接收响应结果
        CloseableHttpResponse response = httpClient.execute(httpGet);

        //4.获取服务端返回的状态码
        int statusCode = response.getStatusLine().getStatusCode();
        System.out.println("服务端返回的状态码为:"+statusCode);

        //5.获取服务端返回的响应体(数据)
        HttpEntity entity = response.getEntity();
        String body = EntityUtils.toString(entity);
        System.out.println("服务端返回的数据为:"+body);

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

1.2.2 POST方式请求

实现步骤:

  1. 创建HttpClient对象
  2. 创建HTTP请求对象
    1. 设置url
    2. 使用fastJson包下的JSONObject通过put方法,将键值对赋值给jsonObject对象
    3. 使用toString方法,将jsonObject赋值给StringEntity对象
    4. 指定请求的编码方式
    5. 指定数据格式
    6. 向httpPost中放入StringEntity对象,即请求体中的Json数据
  3. 发送请求,接受响应结果

  4. 解析结果

    1. 状态码

    2. 响应体:getEntity接收响应体,EntityUtils解析响应体获得body数据

  5. 关闭资源

    1. 关闭响应

    2. 关闭HttpClient

    /**
     * 测试通过HttpClient发送POST方式请求
     */
    @Test
    public void testPOST() throws Exception{
        //1.创建HttpClient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();

        //2.创建HTTP请求对象
        HttpPost httpPost = new HttpPost("http://localhost:8090/admin/employee/login");
        //要通过请求体,以Json格式发送请求

        //使用fastJson包下的JSONObject通过put方法,将键值对赋值给jsonObject对象
        JSONObject jsonObject = new JSONObject();//fastJson包
        jsonObject.put("username","user");
        jsonObject.put("password","123456");
        //使用toString方法,将jsonObject赋值给StringEntity对象
        StringEntity entity = new StringEntity(jsonObject.toString());
        //指定请求的编码方式
        entity.setContentEncoding("utf-8");
        //指定数据格式
        entity.setContentType("application/json");
        //向httpPost中放入StringEntity对象,即请求体中的Json数据
        httpPost.setEntity(entity);

        //3.发送请求,并接收响应结果
        CloseableHttpResponse response = httpClient.execute(httpPost);

        //4.获取服务端返回的状态码
        int statusCode = response.getStatusLine().getStatusCode();
        System.out.println("服务端返回的状态码为:"+statusCode);

        //5.获取服务端返回的响应体(数据)
        HttpEntity responseEntity = response.getEntity();
        String body = EntityUtils.toString(responseEntity);
        System.out.println("服务端返回的数据为:"+body);

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

1.3 HttpClient工具类

package com.sky.utils;

import com.alibaba.fastjson.JSONObject;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Http工具类
 */
public class HttpClientUtil {

    static final  int TIMEOUT_MSEC = 5 * 1000;

    /**
     * 发送GET方式请求
     * @param url
     * @param paramMap
     * @return
     */
    public static String doGet(String url,Map<String,String> paramMap){
        // 创建Httpclient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();

        String result = "";
        CloseableHttpResponse response = null;

        try{
            URIBuilder builder = new URIBuilder(url);
            if(paramMap != null){
                for (String key : paramMap.keySet()) {
                    builder.addParameter(key,paramMap.get(key));
                }
            }
            URI uri = builder.build();

            //创建GET请求
            HttpGet httpGet = new HttpGet(uri);

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

            //判断响应状态
            if(response.getStatusLine().getStatusCode() == 200){
                result = EntityUtils.toString(response.getEntity(),"UTF-8");
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                response.close();
                httpClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return result;
    }

    /**
     * 发送POST方式请求
     * @param url
     * @param paramMap
     * @return
     * @throws IOException
     */
    public static String doPost(String url, Map<String, String> paramMap) throws IOException {
        // 创建Httpclient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();
        CloseableHttpResponse response = null;
        String resultString = "";

        try {
            // 创建Http Post请求
            HttpPost httpPost = new HttpPost(url);

            // 创建参数列表
            if (paramMap != null) {
                List<NameValuePair> paramList = new ArrayList();
                for (Map.Entry<String, String> param : paramMap.entrySet()) {
                    paramList.add(new BasicNameValuePair(param.getKey(), param.getValue()));
                }
                // 模拟表单
                UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList);
                httpPost.setEntity(entity);
            }

            httpPost.setConfig(builderRequestConfig());

            // 执行http请求
            response = httpClient.execute(httpPost);

            resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
        } catch (Exception e) {
            throw e;
        } finally {
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return resultString;
    }

    /**
     * 发送POST方式请求
     * @param url
     * @param paramMap
     * @return
     * @throws IOException
     */
    public static String doPost4Json(String url, Map<String, String> paramMap) throws IOException {
        // 创建Httpclient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();
        CloseableHttpResponse response = null;
        String resultString = "";

        try {
            // 创建Http Post请求
            HttpPost httpPost = new HttpPost(url);

            if (paramMap != null) {
                //构造json格式数据
                JSONObject jsonObject = new JSONObject();
                for (Map.Entry<String, String> param : paramMap.entrySet()) {
                    jsonObject.put(param.getKey(),param.getValue());
                }
                StringEntity entity = new StringEntity(jsonObject.toString(),"utf-8");
                //设置请求编码
                entity.setContentEncoding("utf-8");
                //设置数据类型
                entity.setContentType("application/json");
                httpPost.setEntity(entity);
            }

            httpPost.setConfig(builderRequestConfig());

            // 执行http请求
            response = httpClient.execute(httpPost);

            resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
        } catch (Exception e) {
            throw e;
        } finally {
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return resultString;
    }
    private static RequestConfig builderRequestConfig() {
        return RequestConfig.custom()
                .setConnectTimeout(TIMEOUT_MSEC)
                .setConnectionRequestTimeout(TIMEOUT_MSEC)
                .setSocketTimeout(TIMEOUT_MSEC).build();
    }

}

2. 微信小程序入门

2.1 小程序目录结构

小程序包含一个描述整体程序的 app 和多个描述各自页面的 page。一个小程序主体部分由三个文件组成,必须放在项目的根目录,如下:

文件说明:

app.js:必须存在,主要存放小程序的逻辑代码

app.json:必须存在,小程序配置文件,主要存放小程序的公共配置

app.wxss: 非必须存在,主要存放小程序公共样式表,类似于前端的CSS样式

对小程序主体三个文件了解后,其实一个小程序又有多个页面。比如说,有商品浏览页面、购物车的页面、订单支付的页面、商品的详情页面等等。那这些页面会放在哪呢? 会存放在pages目录。

每个小程序页面主要由四个文件组成:

文件说明:

js文件:必须存在,存放页面业务逻辑代码,编写的js代码。

wxml文件:必须存在,存放页面结构,主要是做页面布局,页面效果展示的,类似于HTML页面。

json文件:非必须,存放页面相关的配置。

wxss文件:非必须,存放页面样式表,相当于CSS文件。

2.2 编写和编译小程序

1)编写

新建一个home文件夹,结构如下:

在app.json中配置home路径,将home放在最上层,代表默认页面为home

在wxml中编写页面布局,在js中编写页面逻辑

<view class="container">
  <!-- 插值显示信息 -->
  <view>{{msg}}</view>

  <!-- 获取用户信息 -->
  <view>
    <!-- 只保留 bindtap,触发 getUserInfo 方法 -->
    <button bindtap="getUserInfo" type="primary">获取用户信息</button>
    
    <!-- 显示用户信息 -->
    <view wx:if="{{nickName}}">
      昵称:{{nickName}}
      <image style="width: 100px; height: 100px; margin-top: 20rpx;" src="{{url}}"></image>
    </view>
  </view>

  <!-- 获取登录用户授权码 -->
  <view>
    <button type="warn" bind:tap="wxLogin">微信登录</button>
    授权码:{{code}}
  </view>

  <!-- 实现异步请求 -->
  <view>
    <button bind:tap="sendRequest" type="default" >发送请求</button>
  </view>

</view>
Page({
	data: {
	  msg: 'hello world',
	  nickName: '', // 存储昵称
	  url: '',       // 存储头像地址
	  code: ''
	},
  
	// 获取微信用户头像和昵称(修正后)
	getUserInfo() {
	  // 关键修正:wx.getUserProfile({...}) 去掉多余的 ()
	  wx.getUserProfile({
		desc: '获取用户信息用于展示头像和昵称', // 必填项,说明用途(不能空)
		success: (res) => {
		  console.log('获取用户信息成功:', res.userInfo);
		  // 更新数据到页面
		  this.setData({
			nickName: res.userInfo.nickName, // 正确获取昵称
			url: res.userInfo.avatarUrl      // 正确获取头像地址
		  });
		},
		fail: (err) => {
		  console.log('用户拒绝授权:', err);
		  // 可选:提示用户授权才能使用功能
		  wx.showToast({
			title: '您拒绝了授权,无法显示信息',
			icon: 'none',
			duration: 1500
		  });
		}
	  });
	},

	//微信登录,获取微信用户的授权码
	wxLogin(){
		wx.login({
		  success: (res) => {
			console.log('用户授权码:'+ res.code)
			this.setData({
				code : res.code
			})
		  },
		})
	},

	//向后端发送请求
	sendRequest(){
		wx.request({
		  url: 'http://localhost:8090/user/shop/status',
		  method : 'GET',
		  success: (res)=>{
			  console.log(res.data)
		  }
		})
	}
  })

2). 运行效果

点击发送请求

因为请求http://localhost:8090/user/shop/status,先要启动后台项目。

注:设置不校验合法域名,若不勾选,请求发送失败。

3. 微信登录

微信登录:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html

流程图:

说明

  1. 调用 wx.login() 获取 临时登录凭证code ,并回传到开发者服务器。

  2. 调用 auth.code2Session 接口,换取 用户唯一标识 OpenID 、 用户在微信开放平台账号下的唯一标识UnionID(若当前小程序已绑定到微信开放平台账号) 和 会话密钥 session_key

3.1 postman模拟微信登录

接口说明:

调用方式

HTTPS 调用


GET https://api.weixin.qq.com/sns/jscode2session 

请求参数

属性 类型 必填 说明
appid string 小程序 appId
secret string 小程序 appSecret
js_code string 登录时获取的 code,可通过wx.login获取
grant_type string 授权类型,此处只需填写 authorization_code

返回参数

属性 类型 说明
session_key string 会话密钥
unionid string 用户在开放平台的唯一标识符,若当前小程序已绑定到微信开放平台帐号下会返回,详见 UnionID 机制说明
errmsg string 错误信息,请求失败时返回
openid string 用户唯一标识
errcode int32 错误码,请求失败时返回

3.2 实现微信登录

3.2.1 需求分析

用户进入到小程序的时候,微信授权登录之后才能点餐。需要获取当前微信用户的相关信息,比如昵称、头像等,这样才能够进入到小程序进行下单操作。是基于微信登录来实现小程序的登录功能,没有采用传统账户密码登录的方式。若第一次使用小程序来点餐,就是一个新用户,需要把这个新的用户保存到数据库当中完成自动注册。

业务规则:

  • 基于微信登录实现小程序的登录功能

  • 如果是新用户需要自动完成注册

3.2.2 接口设计

接口功能描述:微信小程序在弹出弹窗点击允许后,会向后台发送一个login请求并将code作为请求参数,后台需要完成:

  1. 接受请求,向微信服务接口发送请求获取openid
  2. 为微信用户生成jwt令牌
  3. 将用户id、openid、jwt令牌响应返回给微信小程序

3.2.3 代码开发

1)定义相关配置

向微信服务接口发送请求一共需要四个数据,其中有两个数据是写死的,为了统一管理,将这两个配置写在yml文件中,并使用一个properties类接收他们。

application.yml:

sky:
  wechat:
    appid: ${sky.wechat.appid}
    secert: ${sky.wechat.secert}

application-dev.yml:

sky:
  wechat:
    appid: wx774d22aeca128fb51
    secret: 9b89859292b149754f3c01156b3dda76c

WeChatProperties:

@Component
@ConfigurationProperties(prefix = "sky.wechat")
@Data
public class WeChatProperties {

    private String appid; //小程序的appid
    private String secret; //小程序的秘钥
    private String mchid; //商户号
    private String mchSerialNo; //商户API证书的证书序列号
    private String privateKeyFilePath; //商户私钥文件
    private String apiV3Key; //证书解密的密钥
    private String weChatPayCertFilePath; //平台证书
    private String notifyUrl; //支付成功的回调地址
    private String refundNotifyUrl; //退款成功的回调地址

}

设计对应的DTO类来接收请求参数、对应的VO来作为相应返回泛型。

/**
 * C端用户登录
 */
@Data
public class UserLoginDTO implements Serializable {

    private String code;

}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserLoginVO implements Serializable {

    private Long id;
    private String openid;
    private String token;

}

2)后台实现

注意点:

  1. Controller层:jwt令牌生成过程
  2. Service层:使用User作为返回类型;使用HttpClient向微信服务接口发送请求;使用JSONObject接收响应的json串;将获取openid方法提出来作为一个独立的方法。
  3. Mapper层:使用 useGeneratedKeys="true" keyProperty="id" 返回新插入数据的主键id

Controller:

    /**
     * 微信用户登录
     * @param userLoginDTO
     * @return
     */
    @PostMapping("/login")
    @ApiOperation(value = "用户登录接口")
    public Result<UserLoginVO> login(@RequestBody UserLoginDTO userLoginDTO){
        log.info("微信用户登录:{}",userLoginDTO.getCode());
        //微信登录
        User user = userService.wxLogin(userLoginDTO);
        //为微信用户生成jwt令牌
        Map<String, Object> claims = new HashMap<>();
        claims.put(JwtClaimsConstant.USER_ID,user.getId());
        String token = JwtUtil.createJWT(jwtProperties.getUserSecretKey(), jwtProperties.getUserTtl(), claims);

        UserLoginVO userLoginVO = UserLoginVO.builder()
                .id(user.getId())
                .openid(user.getOpenid())
                .token(token)
                .build();
        return Result.success(userLoginVO);
    }

Service:

@Service
public class UserServiceImpl implements UserService {
    //微信服务器接口设为常量
    public static final String WX_LOGIN = "https://api.weixin.qq.com/sns/jscode2session";

    @Autowired
    private WeChatProperties weChatProperties;
    @Autowired
    private UserMapper userMapper;

    /**
     * 微信用户登录
     * @param userLoginDTO
     * @return
     */
    @Override
    public User wxLogin(UserLoginDTO userLoginDTO) {
        //1.调用微信服务器接口获取当前用户的openId
//        Map<String,String> map = new HashMap<>();
//        map.put("appid",weChatProperties.getAppid());
//        map.put("secert",weChatProperties.getSecret());
//        map.put("js_code",userLoginDTO.getCode());
//        map.put("grant_type","authorization_code");
//        String json = HttpClientUtil.doGet(WX_LOGIN, map);
//
//        //fastJson包中
//        JSONObject jsonObject = JSON.parseObject(json);
//        String openid = jsonObject.getString("openid");
        String openid = getOpenid(userLoginDTO.getCode());

        //2.判断openId是否为空,如果为空则登录失败,抛出业务异常。
        if(openid == null){
            throw new LoginFailedException(MessageConstant.LOGIN_FAILED);
        }

        //3.openId不为空,判断当前用户是否为新用户
        User user = userMapper.getByOpenId(openid);
        if (user == null){
             user = User.builder()
                    .openid(openid)
                    .createTime(LocalDateTime.now())
                    .build();
             //4.如果是新用户,则需要自动完成注册
            userMapper.insert(user);
        }
        
        //5.返回用户对象
        return user;
    }

    /**
     * 调用微信接口服务,获取登录用户的openid
     * @param code
     * @return
     */
    private String getOpenid(String code){
        Map<String,String> map = new HashMap<>();
        map.put("appid",weChatProperties.getAppid());
        map.put("secret",weChatProperties.getSecret());
        map.put("js_code",code);
        map.put("grant_type","authorization_code");
        String json = HttpClientUtil.doGet(WX_LOGIN, map);

        //fastJson包中
        JSONObject jsonObject = JSON.parseObject(json);
        String openid = jsonObject.getString("openid");
        return openid;
    }
}

Mapper:

@Mapper
public interface UserMapper {

    @Select("select * from user where openid = #{openid}")
    User getByOpenId(String openid);


    void insert(User user);
}
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
        insert into user (openid, name, phone, sex, id_number, avatar, create_time)
        values (#{openid}, #{name}, #{phone}, #{sex}, #{idNumber}, #{avatar}, #{createTime})
    </insert>

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐