HttpClient
介绍
HttpClient 是 Apache 提供的一个开源的 Java HTTP 客户端库,用于发送 HTTP 请求和处理 HTTP 响应。它提供了一种更简便的方式来执行 HTTP 请求,并支持多种协议,如 HTTP、HTTPS、FTP 等。
使用 HttpClient 可以方便地与远程服务器进行通信,发送 HTTP 请求并处理响应。在实际应用中,HttpClient 常被用于与 RESTful API 交互、爬虫开发、测试等场景。
入门案例
新建一个test类进行测试
package com.sky.test;
import org.apache.http.HttpEntity;
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.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.json.JSONObject;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
@SpringBootTest
public class HttpClientTest {
/**
* 测试通过HttpClientTest发送GET请求
*/
@Test
public void testGet() throws Exception{
//创建HTTP对象
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();
}
/**
* 测试通过HttpClientTest发送POST请求
*/
@Test
public void testPost() throws Exception {
//创建HTTP对象
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();
}
}
微信小程序开发
介绍
准备工作
注册小程序
登录微信小程序平台按照提示进行注册
完善小程序信息
登录后进入开发管理和管理,根据页面提示信息补充小程序各项基本信息并生成密钥。
下载开发者工具
根据提示创建小程序
入门案例
获取用户信息
<!--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>
{{meg}}
</view>
<button type="primary" bind:tap="getUserInfo">获取用户信息</button>
昵称: {{nickName}}
<image src="{{url}}" style="width: 100px;height: 100px;"></image>
</view>
</scroll-view>
// index.js
Page({
data:{
meg:'hello world',
nickName:'',
url:''
},
//获取微信用户头像和昵称
getUserInfo(){
wx.getUserProfile({
desc: '获取用户信息',
success: (res) =>{
console.log(res.userInfo)
//为数据赋值
this.setData({
nickName: res.userInfo.nickName,
url: res.userInfo.avatarUrl
})
}
})
}
})
其中需要调整数据库到2.7以下版本。具体路径为右上角详情->本地设置->调试基础库。
获取用户授权码
data:{
meg:'hello world',
nickName:'',
url:'',
code:''
},
//微信登录,获取微信登录的授权码
wxlogin(){
wx.login({
success: (res) => {
console.log(res.code)
this.setData({
code: res.code
})
},
})
},
<view>
<button type="warn" bind:tap="wxlogin">微信登录</button>
授权码:{{code}}
</view>
发送请求
//发送请求
sendRequest(){
wx.request({
url: 'http://localhost:8080/user/shop/status',
method:'GET',
success: (res)=>{
//代表后端响应的整个JSON数据
console.log(res.data)
}
})
}
<view>
<button type="default" bind:tap="sendRequest">发送请求</button>
</view>
微信登录
微信登录流程
需求分析
代码开发
openid是微信用户的唯一标识
新建Controller层
@RestController
@RequestMapping("/user/user")
@Api(tags = "C端用户相关接口")
@Slf4j
public class UserController {
@Autowired
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);
//为微信用户生成令牌
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
@Slf4j
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) {
String openid = getOpenid(userLoginDTO.getCode());
//判断openid是否为空,如果为空表示登录失败,抛出业务异常
if(openid == null){
throw new LoginFailedException(MessageConstant.LOGIN_FAILED);
}
//是否是新用户
User user = userMapper.getByOpenID(openid);
//是新用户,自动完成注册
if (user==null){
user = User.builder()
.openid(openid)
.createTime(LocalDateTime.now())
.build();
}
userMapper.insert(user);
//返回用户对象
return user;
}
private String getOpenid(String code){
//调用微信服务器接口服务,获得当前微信服务的openid
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);
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);
/**
* 插入数据
* @param user
*/
void insert(User user);
}
后端对应的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>
配置web层、拦截器组件学习
对注射web层中WebMvcConfiguration的理解
/**
* 设置静态资源映射
* 当访问 /doc.html路径时,Spring MVC将会去 classpath:/META-INF/resources/目录下寻找对应的静态资源
* 而访问 /webjars/** 路径时,将会去 classpath:/META-INF/resources/webjars/目录下寻找对应的静态资源。
* 这样做的好处是可以将一些静态资源集中存放在指定的目录,而不需要暴露给外部直接访问项目的文件结构。
* 这有助于更好地组织项目结构,同时提供对静态资源的有效管理和映射。
* @param registry
*/
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
log.info("开始设置静态资源映射");
registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
/**
* 扩展Spring MVC框架的消息转换器
* 将 Java 对象转换为 JSON 数据:当服务器端需要将 Java对象转换为 JSON数据
* 以便于发送给客户端时,消息转换器负责将 Java对象序列化为JSON格式的数据。
* 将 JSON数据转换为 Java对象:当客户端发送包含JSON数据的请求体给服务器时
* 消息转换器负责将接收到的JSON数据反序列化为对应的 Java对象。
* @param converters
*/
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
log.info("扩展消息转换器..");
//创建一个消息转换器对象
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
//需要为消息转换器设置一个对象转换器,可以将Java对象序列化为json数据
converter.setObjectMapper(new JacksonObjectMapper());
//将自己的消息转换器加入容器中
converters.add(0,converter);
}
/**
* 注册自定义拦截器
* 第一个拦截器 jwtTokenAdminInterceptor 注册在路径 /admin/**下,但排除了路径/admin/employee/login。
* 第二个拦截器 jwtTokenUserInterceptor 注册在路径 /user/**下,但排除了路径/user/user/login和/user/shop/status。
* 这样配置的效果是,当请求路径匹配拦截路径时,会触发相应的拦截器执行相应的逻辑。拦截器可以用于处理请求前的预处理、日志记录、权限验证等工作。
* 通过拦截器来验证 JWT 令牌,确保请求的合法性,这在安全性要求较高的 Web 应用中是常见的做法。
* @param registry
*/
protected void addInterceptors(InterceptorRegistry registry) {
log.info("开始注册自定义拦截器...");
registry.addInterceptor(jwtTokenAdminInterceptor)
.addPathPatterns("/admin/**")
.excludePathPatterns("/admin/employee/login");
registry.addInterceptor(jwtTokenUserInterceptor)
.addPathPatterns("/user/**")
.excludePathPatterns("/user/user/login")
.excludePathPatterns("/user/shop/status");
}
对jwt拦截器的理解
/**
* jwt令牌校验的拦截器
*/
@Component
@Slf4j
public class JwtTokenUserInterceptor implements HandlerInterceptor {
@Autowired
private JwtProperties jwtProperties;
/**
* 校验jwt
*
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//获取当前线程id
System.out.println("当前线程id"+Thread.currentThread().getId());
//判断当前拦截到的是Controller的方法还是其他资源
if (!(handler instanceof HandlerMethod)) {
//当前拦截到的不是动态方法,直接放行
return true;
}
//1、从请求头中获取令牌
String token = request.getHeader(jwtProperties.getUserTokenName());
//2、校验令牌
try {
log.info("jwt校验:{}", token);
//这个方法是自定义的 JWT 解析方法,接受两个参数,第一个是用于解密的密钥
//第二个是待解析的 JWT 令牌 (token)。该方法返回一个 Claims 对象,其中包含了 JWT 中的声明信息。
Claims claims = JwtUtil.parseJWT(jwtProperties.getUserSecretKey(), token);
//这一行代码从 Claims 对象中获取用户ID的声明,并将其转换成Long类型。在JWT的声明中,通常会包含一些声明(比如过期时间、签发者等)
// 同时也会包含自定义的声明。在这里,通过 JwtClaimsConstant.USER_ID 来获取用户ID的声明。
Long userId = Long.valueOf(claims.get(JwtClaimsConstant.USER_ID).toString());
log.info("当前用户id:", userId);
//在拦截器中存入
//这行代码的作用是将用户ID存入线程上下文。线程上下文是一个与线程关联的数据存储区域,可以在整个线程的生命周期内共享数据。
//在这个具体的场景中,当用户的 JWT 令牌验证通过后,将用户的ID存入线程上下文,以便在后续的业务逻辑中能够方便地获取当前用户的ID,
//而不必在每个方法参数中传递用户ID或者从其他地方再次获取。这样做的好处是简化了代码,提高了代码的可读性和可维护性。
//举例来说,如果有其他地方需要使用当前用户的ID,可以通过 BaseContext.getCurrentId() 获取,
//而不必传递用户ID的参数。这在涉及多个方法、类之间需要传递用户ID的情况下,可以减少重复代码,提高开发效率。
BaseContext.setCurrentId(userId);
//3、通过,放行
return true;
} catch (Exception ex) {
//4、不通过,响应401状态码
response.setStatus(401);
return false;
}
}
}
导入商品浏览功能
需求分析
代码实现
C端-分类接口
Controller层注意对注释进行命名用来区别admin的相同请求,具体实现方法可以通用。
@RestController("userCategoryController")
@Api(tags = "C端-分类接口")
@RequestMapping("/user/category")
@Slf4j
public class CategoryController {
@Autowired
private CategoryService categoryService;
// Spring MVC 默认会按照参数名字和请求中的参数名字进行匹配。在你的情况下,接口路径为 /list
// 而请求参数为 type,由于参数名字和请求中的参数名字一致,Spring MVC 可能会自动将参数值绑定到方法的参数上。
//这是Spring MVC 的一种简化规则,适用于一些简单的情况。
@GetMapping("/list")
@ApiOperation("条件查询")
/**
* 条件查询
*/
public Result<List<Category>> list(Integer type){
List<Category> list = categoryService.list(type);
return Result.success(list);
}
}
C端-菜品浏览接口
Controller层
@RestController("userDishController")
@Api(tags = "C端-菜品浏览接口")
@RequestMapping("/user/dish")
@Slf4j
public class DishController {
@Autowired
private DishService dishService;
/**
* 根据分类id查询菜品
* @param categoryId
* @return
*/
@GetMapping("/list")
@ApiOperation("根据分类id查询菜品")
private Result<List<DishVO>> list(Long categoryId){
List<DishVO> dishVOS = dishService.listWithFlavor(categoryId);
return Result.success(dishVOS);
}
Service实现层
/**
* 根据分类id查询菜品以及口味
* @param categoryId
* @return
*/
@Override
public List<DishVO> listWithFlavor(Long categoryId) {
Dish dish = Dish.builder()
.categoryId(categoryId)
.status(StatusConstant.ENABLE)
.build();
List<Dish> dishList = dishMapper.list(dish);
List<DishVO> dishVOList = new ArrayList<>();
for (Dish d : dishList){
DishVO dishVO = new DishVO();
BeanUtils.copyProperties(d, dishVO);
List<DishFlavor> dishFlavorList = dishFlavorMapper.getByDishId(d.getId());
dishVO.setFlavors(dishFlavorList);
dishVOList.add(dishVO);
}
return dishVOList;
}
C端-套餐浏览接口
Controller层
@RestController("userSetmealController")
@Api(tags = "C端-套餐浏览接口")
@RequestMapping("/user/setmeal")
@Slf4j
public class SetmealController {
@Autowired
private SetmealService setmealService;
/**
* 套餐浏览接口
* @param categoryId
* @return
*/
@GetMapping("/list")
@ApiOperation("根据分类id查询套餐")
public Result<List<Setmeal>> list(Long categoryId){
List<Setmeal> setmeals = setmealService.list(categoryId);
return Result.success(setmeals);
}
/**
* 根据套餐id查询包含的菜品
* @param id
* @return
*/
@ApiOperation("根据套餐id查询包含的菜品")
@GetMapping("/dish/{id}")
public Result<List<DishItemVO>> dishList(@PathVariable("id") Long id){
List<DishItemVO> dishItemVOS = setmealService.dishList(id);
return Result.success(dishItemVOS);
}
Service层实现类
/**
* 根据分类id查询菜品
* @param categoryId
* @return
*/
@Override
public List<Setmeal> list(Long categoryId) {
Setmeal setmeal = new Setmeal();
setmeal.setCategoryId(categoryId);
setmeal.setStatus(StatusConstant.ENABLE);
List<Setmeal> setmeals = setmealMapper.list(setmeal);
return setmeals;
}
/**
* 根据套餐id查询包含的菜品
* @param id
* @return
*/
@Override
public List<DishItemVO> dishList(Long id) {
return setmealMapper.dishList(id);
}
Mapper接口
/**
* 根据分类id查询菜品
* @param Setmeal
* @return
*/
List<Setmeal> list(Setmeal setmeal);
/**
* 根据套餐id查询包含的菜品
* @param setmealId
* @return
*/
@Select("select sd.name,sd.copies,d.image,d.description from setmeal_dish sd left join dish d on sd.dish_id = d.id " +
"where sd.setmeal_id = #{setmealId}")
List<DishItemVO> dishList(Long setmealId);
XML底层
<select id="list" resultType="com.sky.entity.Setmeal" parameterType="Setmeal">
select * from setmeal
<where>
<if test="name != null">
and name like concat('%',#{name},'%')
</if>
<if test="categoryId != null">
and category_id = #{categoryId}
</if>
<if test="status != null">
and status = #{status}
</if>
</where>
</select>