🔥 本文由 程序喵正在路上 原创,优快云首发!
💖 系列专栏:苍穹外卖项目实战
🌠 首发时间:2024年5月9日
🦋 欢迎关注🖱点赞👍收藏🌟留言🐾
目录
HttpClient
介绍
HttpClient 是 Apache Jakarta Common 下的子项目,可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。
HttpClient 作用:
- 发送 HTTP 请求
- 接收响应数据
核心API:
- HttpClient
- HttpClients
- CloseableHttpClient
- HttpGet
- HttpPost
发送请求步骤:
- 创建 HttpClient 对象
- 创建 Http 请求对象
- 调用 HttpClient 的 execute 方法发送请求
使用 HttpClient 需要导入它的 maven 坐标:
由于我们项目中已经导入了阿里云 OSS 的依赖,而阿里云 OSS 的底层已经包含了 HttpClient,所以我们就不用再导入了。当然,再单独导入也没什么问题。
入门案例
GET 方式请求:
新建测试类 HttpClientTest,创建测试方法 testGET:
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.IOException;
@SpringBootTest
public class HttpClientTest {
/**
* 测试通过httpclient发送GET方式的请求
*/
@Test
public void testGET() throws Exception {
//创建httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
//创建请求对象, 请求地址为用户端查询营业状态的地址
HttpGet httpGet = new HttpGet("http://localhost:8080/user/shop/status");
//发送请求, 接收响应结果
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();
}
}
先启动项目和 redis 服务端,再执行 testGET 方法,查看结果:
POST 方式请求:
/**
* 测试通过httpclient发送POST方式的请求
*/
@Test
public void testPOST() throws Exception {
//创建httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
//创建请求对象, 请求地址为管理端登录的地址
HttpPost httpPost = new HttpPost("http://localhost:8080/admin/employee/login");
//构造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();
}
为了方便我们后续使用,项目中已经实现了一个 HttpClient 的工具类:
微信小程序开发
介绍
官网:https://mp.weixin.qq.com/cgi-bin/wx?token=&lang=zh_CN
ps:个人开发的小程序是不支持支付功能的。
准备工作
开发微信小程序之前需要做如下准备工作:
- 注册小程序
- 完善小程序信息
- 下载开发者工具
注册小程序
注册地址:https://mp.weixin.qq.com/wxopen/waregister?action=step1
完善小程序信息
登录小程序后台:https://mp.weixin.qq.com/,点击进入登录,然后自行完善小程序信息、小程序类目
下滑来到开发管理页面,这里的小程序ID和小程序密钥我们后面都会用到,记得保存:
下载开发者工具
下载地址: https://developers.weixin.qq.com/miniprogram/dev/devtools/stable.html
下载安装,登录后来到主页面:
创建小程序项目:
熟悉开发者工具布局:
设置不校验合法域名:
入门案例
操作步骤:
- 了解小程序目录结构
- 编写小程序代码
- 编译小程序
了解小程序目录结构
小程序包含一个描述整体程序的 app 和多个描述各自页面的 page。一个小程序主体部分由三个文件组成,必须放在项目的根目录,如下:
一个小程序页面由四个文件组成:
编写小程序代码
我们在项目自带的这个页面中来写一些小程序的入门案例:
index.wxml
<!--index.wxml-->
<navigation-bar title="Weixin" back="{{false}}" color="black" background="#FFF"></navigation-bar>
<scroll-view class="scrollarea" scroll-y type="list">
<view class="container">
<view>{{msg}}</view>
<!-- 获取用户的头像和昵称 -->
<view>
<button type="default" bind:tap="getUserInfo">获取用户信息</button>
<image style="width: 100px;height: 100px;" src="{{avatarUrl}}"></image>
{{nickName}}
</view>
<!-- 微信登录,获取微信用户的授权码 -->
<view>
<button type="primary" bind:tap="wxlogin">微信登录</button>
<text style="font-size: small;">获取到的授权码: {{code}}</text>
</view>
<!-- 发送请求 -->
<view>
<button type="primary" bind:tap="sendRequest">发送请求</button>
<text style="font-size: small;">响应结果: {{result}}</text>
</view>
</view>
</scroll-view>
index.js
// index.js
Page({
//数据
data: {
msg: 'hello weixin',
avatarUrl: '',
nickName: '',
code: '',
result: '',
},
//获取微信用户的头像和昵称
getUserInfo: function () {
wx.getUserProfile({
desc: '获取用户信息',
success: (res) => {
console.log(res) //显示在控制台
this.setData({
avatarUrl: res.userInfo.avatarUrl,
nickName: res.userInfo.nickName
})
}
})
},
//获取微信用户的授权码
wxlogin: function () {
wx.login({
success: (res) => {
console.log("授权码: " + res.code)
this.setData({
code: res.code
})
},
})
},
//发送请求
sendRequest: function () {
wx.request({
url: 'http://localhost:8080/user/shop/status',
method: 'GET',
success: (res) => {
console.log("响应结果: " + res.data.data)
this.setData({
result: res.data.data
})
}
})
}
})
编译小程序
发布小程序
如果我们的小程序已经开发完,我们可以将其上传到微信的服务器:
然后它就会出现在微信平台的版本管理中的开发版本中,我们需要提交审核,审核通过后小程序才算上线。
微信登录
导入小程序代码
由于小程序开发属于前端的内容,我们这里就不再多讲,直接导入准备好的小程序代码。
需要说明一下的是,如果你的后端服务端口号和图中的不同,那么需要修改一下。
微信登录流程
微信登录:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html
说明
- 调用
wx.login()
获取 临时登录凭证code ,并回传到开发者服务器。 - 调用
auth.code2Session
接口,换取 用户唯一标识 OpenID 、 用户在微信开放平台账号下的唯一标识UnionID(若当前小程序已绑定到微信开放平台账号) 和 会话密钥 session_key。
之后开发者服务器可以根据用户标识来生成自定义登录态,用于后续业务逻辑中前后端交互时识别用户身份。
需求分析和设计
产品原型:
业务规则:
- 基于微信登录实现小程序的登录功能
- 如果是新用户需要自动完成注册
接口设计:
数据库设计(user表):
ps:个人注册的小程序是无法获取到用户的手机号的。
代码开发
配置微信登录所需配置项:
配置为微信用户生成 jwt 令牌时使用的配置项:
DTO 设计:
VO 设计:
新建 UserController,根据接口定义创建 login 方法:
import com.sky.constant.JwtClaimsConstant;
import com.sky.dto.UserLoginDTO;
import com.sky.entity.User;
import com.sky.properties.JwtProperties;
import com.sky.result.Result;
import com.sky.service.UserService;
import com.sky.utils.JwtUtil;
import com.sky.vo.UserLoginVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/user/user")
@Api("C端-用户接口")
@Slf4j
public class UserController {
@Autowired
private UserService userService;
@Autowired
private JwtProperties jwtProperties;
/**
* 微信登录
*
* @param userLoginDTO
* @return
*/
@PostMapping("/login")
@ApiOperation("微信登录")
public Result<UserLoginVO> login(@RequestBody UserLoginDTO userLoginDTO) {
log.info("微信用户登录, 授权码为:{}", userLoginDTO.getCode());
User user = userService.wxLogin(userLoginDTO);
//生成用户端jwt
Map 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);
}
}
创建 UserService 接口,声明 wxLogin 方法:
import com.sky.dto.UserLoginDTO;
import com.sky.entity.User;
public interface UserService {
/**
* 根据微信授权码实现微信登录
*
* @param userLoginDTO
* @return
*/
User wxLogin(UserLoginDTO userLoginDTO);
}
创建 UserServiceImpl 实现类,实现 wxLogin 方法:
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.sky.constant.MessageConstant;
import com.sky.dto.UserLoginDTO;
import com.sky.entity.User;
import com.sky.exception.LoginFailedException;
import com.sky.mapper.UserMapper;
import com.sky.properties.WeChatProperties;
import com.sky.service.UserService;
import com.sky.utils.HttpClientUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.expression.spel.ast.OpNE;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
@Service
@Slf4j
public class UserServiceImpl implements UserService {
public static final String WX_LOGIN = "https://api.weixin.qq.com/sns/jscode2session";
@Autowired
private UserMapper userMapper;
@Autowired
private WeChatProperties weChatProperties;
/**
* 根据微信授权码实现微信登录
*
* @param userLoginDTO
* @return
*/
public User wxLogin(UserLoginDTO userLoginDTO) {
//授权码
String code = userLoginDTO.getCode();
String openid = getOpenid(code);
if (openid == null) {
throw new LoginFailedException(MessageConstant.LOGIN_FAILED);
}
//根据openid查询用户信息
User user = userMapper.getByOpenid(openid);
//用户为小程序的新用户, 将其信息保存起来
if (user == null) {
user = new User();
user.setOpenid(openid);
user.setCreateTime(LocalDateTime.now());
userMapper.insert(user);
}
return user;
}
/**
* 获取微信用户的唯一标识openid
*
* @param code
* @return
*/
private String getOpenid(String code) {
//请求参数封装
Map 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);
log.info("微信登录返回结果:{}", json);
//解析json字符串, 获取openid
JSONObject jsonObject = JSON.parseObject(json);
String openid = jsonObject.getString("openid");
log.info("微信用户的openid为:{}", openid);
return openid;
}
}
创建 UserMapper 接口:
import com.sky.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface UserMapper {
/**
* 根据openid查询用户
*
* @param openid
* @return
*/
@Select("select * from user where openid = #{openid}")
User getByOpenid(String openid);
/**
* 插入新用户
*
* @param user
*/
void insert(User user);
}
创建 UserMapper.xml 映射文件:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.UserMapper">
<!-- 插入新用户-->
<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>
</mapper>
编写拦截器 JwtTokenUserInterceptor,统一拦截用户端发送的请求并进行 jwt 校验:
复制 JwtTokenAdminInterceptor,再稍微修改即可
在 WebMvcConfiguration 配置类中注册拦截器:
功能测试
启动项目,来到小程序端点击登录:
导入商品浏览功能代码
需求分析和设计
产品原型:
接口设计:
-
查询分类
-
根据分类 id 查询菜品
-
根据分类 id 查询套餐
-
根据套餐 id 查询包含的菜品
代码导入
除了 Controller 的代码可以直接拷贝文件,其他的代码都需要打开文件复制对应的代码到原文件中