苍穹外卖 项目记录 第六天
一 HttpClient
HttpClient 是Apache Jakarta Common 下的子项目,可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。
HttpClient作用:
发送HTTP请求
接收响应数据
HttpClient的核心API:
HttpClient:Http客户端对象类型,使用该类型对象可发起Http请求。
HttpClients:可认为是构建器,可创建HttpClient对象。
CloseableHttpClient:实现类,实现了HttpClient接口。
HttpGet:Get方式请求类型。
HttpPost:Post方式请求类型。
HttpClient发送请求步骤:
创建HttpClient对象
创建Http请求对象
调用HttpClient的execute方法发送请求
二 微信小程序开发
小程序是一种新的开放能力,开发者可以快速地开发一个小程序。可以在微信内被便捷地获取和传播,同时具有出色的使用体验。
开发微信小程序之前需要做如下准备工作:
注册小程序
完善小程序信息
下载开发者工具
1). 注册小程序
注册地址:https://mp.weixin.qq.com/wxopen/waregister?action=step1
2). 完善小程序信息
登录小程序后台:https://mp.weixin.qq.com/
完善小程序信息、小程序类目
在开发管理页面查看小程序的 AppID
3). 下载开发者工具
资料中已提供,无需下载,熟悉下载步骤即可。一定要在官网下载最新稳定版
下载地址: https://developers.weixin.qq.com/miniprogram/dev/devtools/stable.html
开发阶段,小程序发出请求到后端的Tomcat服务器,一定要勾选图中"不校验"的设置。若不勾选,请求发送失败。
调试基础库为较低的版本,建议使用2.25.xx 版本的
小程序包含一个描述整体程序的 app 和多个描述各自页面的 page。一个小程序主体部分由三个文件组成,必须放在项目的根目录:
app.js:必须存在,主要存放小程序的逻辑代码
app.json:必须存在,小程序配置文件,主要存放小程序的公共配置
app.wxss:非必须存在,主要存放小程序公共样式表,类似于前端的CSS样式
每个小程序页面主要由四个文件组成:
js文件:必须存在,存放页面业务逻辑代码,编写的js代码。
wxml文件:必须存在,存放页面结构,主要是做页面布局,页面效果展示的,类似于HTML页面。
json文件:非必须,存放页面相关的配置。
wxss文件:非必须,存放页面样式表,相当于CSS文件。
发布小程序->审核版本->线上版本
三 微信登录
导入小程序代码
在导入时可能会自动填充APPID,请将其修改为自己的APPID,后端服务改为不使用云服务,修改为自己后端服务的ip地址和端口号(默认不需要修改)
微信登录流程
微信登录:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html
步骤分析:
小程序端,调用wx.login()获取code,就是授权码。
小程序端,调用wx.request()发送请求并携带code,请求开发者服务器(自己编写的后端服务)。
开发者服务端,通过HttpClient向微信接口服务发送请求,并携带appId+appsecret+code三个参数。
开发者服务端,接收微信接口服务返回的数据,session_key+opendId等。opendId是微信用户的唯一标识。
开发者服务端,自定义登录态,生成令牌(token)和openid等数据返回给小程序端,方便后绪请求身份校验。
小程序端,收到自定义登录态,存储storage。
小程序端,后绪通过wx.request()发起业务请求时,携带token。
开发者服务端,收到请求后,通过携带的token,解析当前登录用户的id。
开发者服务端,身份校验通过后,继续相关的业务逻辑处理,最终返回业务数据。
需求分析和设计
业务规则:
基于微信登录实现小程序的登录功能
如果是新用户需要自动完成注册
说明:请求路径/user/user/login,第一个user代表用户端,第二个user代表用户模块。
代码开发
定义相关配置
配置微信登录所需配置项:
application-dev.yml
sky: wechat: appid: 自己的小程序的id secret: 自己的小程序的密钥(在开发管理页面获取或重置)application.yml
sky: wechat: appid: ${sky.wechat.appid} secret: ${sky.wechat.secret}配置为微信用户生成jwt令牌时使用的配置项:
application.yml
sky: jwt: # 设置jwt签名加密时使用的秘钥 admin-secret-key: itcast # 设置jwt过期时间 admin-ttl: 7200000 # 设置前端传递过来的令牌名称 admin-token-name: token user-secret-key: itheima user-ttl: 7200000 user-token-name: authenticationController层
根据接口定义创建UserController的login方法:
@RestController @RequestMapping("/user/user") @Api(tags = "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<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); } }其中,JwtClaimsConstant.USER_ID常量已定义。
Service层接口
创建UserService接口:
public interface UserService { /** * 微信登录 * @param userLoginDTO * @return */ User wxLogin(UserLoginDTO userLoginDTO); }Service层实现类
创建UserServiceImpl实现类:实现获取微信用户的openid和微信登录功能
@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 */ 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; } /** * 调用微信接口服务,获取微信用户的openid * @param code * @return */ 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层
创建UserMapper接口:
@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校验
/** * 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 { //判断当前拦截到的是Controller的方法还是其他资源 if (!(handler instanceof HandlerMethod)) { //当前拦截到的不是动态方法,直接放行 return true; } //1、从请求头中获取令牌 String token = request.getHeader(jwtProperties.getUserTokenName()); //2、校验令牌 try { log.info("jwt校验:{}", token); Claims claims = JwtUtil.parseJWT(jwtProperties.getUserSecretKey(), token); Long userId = Long.valueOf(claims.get(JwtClaimsConstant.USER_ID).toString()); log.info("当前用户的id:", userId); BaseContext.setCurrentId(userId); //3、通过,放行 return true; } catch (Exception ex) { //4、不通过,响应401状态码 response.setStatus(401); return false; } } }在WebMvcConfiguration配置类中注册拦截器:
@Autowired private JwtTokenUserInterceptor jwtTokenUserInterceptor; /** * 注册自定义拦截器 * @param registry */ protected void addInterceptors(InterceptorRegistry registry) { log.info("开始注册自定义拦截器..."); //......... registry.addInterceptor(jwtTokenUserInterceptor) .addPathPatterns("/user/**") .excludePathPatterns("/user/user/login") .excludePathPatterns("/user/shop/status"); }启动微信小程序编译时先检查:
1.检查后端有没有打开
2.检查redis有没有打开
3.调试基础库要选低版本的倒数几个2.xx的
四 导入商品浏览功能代码
需求分析和设计
接口设计:
查询分类
根据分类id查询菜品
根据分类id查询套餐
根据套餐id查询包含的菜品
分析每个接口,明确每个接口的请求方式、请求路径、传入参数和返回值。
代码导入
可按照mapper-->service-->controller依次导入,这样代码不会显示相应的报错。
进入到sky-server模块中
Mapper层
在SetmealMapper.java中添加list和getDishItemBySetmealId两个方法
/** * 动态条件查询套餐 * @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> getDishItemBySetmealId(Long setmealId);创建SetmealMapper.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.SetmealMapper"> <select id="list" parameterType="Setmeal" resultType="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> </mapper>Service层
创建SetmealService.java
public interface SetmealService { /** * 条件查询 * @param setmeal * @return */ List<Setmeal> list(Setmeal setmeal); /** * 根据id查询菜品选项 * @param id * @return */ List<DishItemVO> getDishItemById(Long id); }创建SetmealServiceImpl.java
/** * 套餐业务实现 */ @Service @Slf4j public class SetmealServiceImpl implements SetmealService { @Autowired private SetmealMapper setmealMapper; @Autowired private SetmealDishMapper setmealDishMapper; @Autowired private DishMapper dishMapper; /** * 条件查询 * @param setmeal * @return */ public List<Setmeal> list(Setmeal setmeal) { List<Setmeal> list = setmealMapper.list(setmeal); return list; } /** * 根据id查询菜品选项 * @param id * @return */ public List<DishItemVO> getDishItemById(Long id) { return setmealMapper.getDishItemBySetmealId(id); } }在DishService.java中添加listWithFlavor方法定义
/** * 条件查询菜品和口味 * @param dish * @return */ List<DishVO> listWithFlavor(Dish dish);在DishServiceImpl.java中实现listWithFlavor方法
/** * 条件查询菜品和口味 * @param dish * @return */ public List<DishVO> listWithFlavor(Dish dish) { List<Dish> dishList = dishMapper.list(dish); List<DishVO> dishVOList = new ArrayList<>(); for (Dish d : dishList) { DishVO dishVO = new DishVO(); BeanUtils.copyProperties(d,dishVO); //根据菜品id查询对应的口味 List<DishFlavor> flavors = dishFlavorMapper.getByDishId(d.getId()); dishVO.setFlavors(flavors); dishVOList.add(dishVO); } return dishVOList; }Controller层
创建DishController.java
@RestController("userDishController") @RequestMapping("/user/dish") @Slf4j @Api(tags = "C端-菜品浏览接口") public class DishController { @Autowired private DishService dishService; /** * 根据分类id查询菜品 * * @param categoryId * @return */ @GetMapping("/list") @ApiOperation("根据分类id查询菜品") public Result<List<DishVO>> list(Long categoryId) { Dish dish = new Dish(); dish.setCategoryId(categoryId); dish.setStatus(StatusConstant.ENABLE);//查询起售中的菜品 List<DishVO> list = dishService.listWithFlavor(dish); return Result.success(list); } }创建CategoryController.java
@RestController("userCategoryController") @RequestMapping("/user/category") @Api(tags = "C端-分类接口") public class CategoryController { @Autowired private CategoryService categoryService; /** * 查询分类 * @param type * @return */ @GetMapping("/list") @ApiOperation("查询分类") public Result<List<Category>> list(Integer type) { List<Category> list = categoryService.list(type); return Result.success(list); } }创建SetmealController.java
@RestController("userSetmealController") @RequestMapping("/user/setmeal") @Api(tags = "C端-套餐浏览接口") public class SetmealController { @Autowired private SetmealService setmealService; /** * 条件查询 * * @param categoryId * @return */ @GetMapping("/list") @ApiOperation("根据分类id查询套餐") public Result<List<Setmeal>> list(Long categoryId) { Setmeal setmeal = new Setmeal(); setmeal.setCategoryId(categoryId); setmeal.setStatus(StatusConstant.ENABLE); List<Setmeal> list = setmealService.list(setmeal); return Result.success(list); } /** * 根据套餐id查询包含的菜品列表 * * @param id * @return */ @GetMapping("/dish/{id}") @ApiOperation("根据套餐id查询包含的菜品列表") public Result<List<DishItemVO>> dishList(@PathVariable("id") Long id) { List<DishItemVO> list = setmealService.getDishItemById(id); return Result.success(list); } }总结
今天主要学习了HttpClient的基础知识,简单的微信小程序开发,开发微信登录小程序的功能代码和导入商品浏览功能代码,学习了用户端的功能开发,完善了外卖项目。
靡不有初,鲜克有终。
