前言
本篇文章用于记录苍穹外卖Day06微信登录、商品浏览的学习。
要完成微信登录功能,我们需要用到HttpClient,所以在介绍微信登录功能前,先介绍一下HttpClient
HttpClient
HttpClient可以提供高效的、功能丰富的支持Http协议的客户端编程工具包。允许我们在Java程序中通过编码的方式向客户端发送Http请求(GET、POST…)。
核心API:
- HttpClient
- HttpClients
- CloseableHttpClient
- HttpGet
- HttpPost
HttpClient为一个接口,CloseableHttpClient为其实现类。
发送请求步骤:
- 创建HttpClient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
- 创建Http请求对象(HttpGet、HttpPost)
HttpGet httpGet=new HttpGet("http://localhost:9010/user/shop/status");
- 调用HttpClient的execute方法发送请求
CloseableHttpResponse response=httpClient.execute(httpGet);
- 最后就是将返回的数据做个解析以及释放资源
微信登录
关于微信小程序端我就直接导入代码了(按照老师的导入的话可能出现报错,这里导入时我的后端服务选择的是不使用云服务),对小程序开发感兴趣的可以去深入了解一下,这里把主要工作放在后端代码开发。
在开发微信登录接口之前,我先来简单介绍一下微信登录的流程:
- 首先小程序就是微信小程序,开发者服务器就是我们编写的后端服务,微信接口服务是由微信官方提供的服务。
- 在我们的小程序端点击确定去登录时,会调用login()方法生成一个code(如下图所示)
- 此时我们的后端服务要接收到这个code和其他参数(具体如下所示),发送请求调用微信接口服务为我们生成返回一些数据如session_key、openid,其中openid是我们微信用户的一个唯一标识,此时我们就可以根据openid为用户生成一个token令牌,每次访问都携带这个令牌进行数据访问
那么正式进入到微信登录功能的代码开发:
- UserController,在controller层,我们发送登录请求之后,响应成功后会返回一个微信用户唯一标识openid,我们要根据这个openid生成一个JWT令牌,后续用户都要携带这个JWT令牌进行数据访问。同时我们要封装好一个UserLoginVO返回给小程序端。
@RestController
@Slf4j
@RequestMapping("/user/user")
@Api(tags = "用户登录接口")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private JwtProperties jwtProperties;
@PostMapping("/login")
@ApiOperation("微信登录")
public Result<UserLoginVO> login(@RequestBody UserLoginDTO userLoginDTO){
log.info("微信登录:{}",userLoginDTO.getCode());
//微信登录
User user=userService.login(userLoginDTO);
//生成jwt令牌
HashMap<String, Object> claims=new HashMap<>();
//JwtClaimsConstant.USER_ID->userId
claims.put(JwtClaimsConstant.USER_ID,user.getId());
String token=JwtUtil.createJWT(jwtProperties.getUserSecretKey(), jwtProperties.getUserTtl(), claims);
//封装UserLoginVO对象返回给小程序端
UserLoginVO userLoginVO=new UserLoginVO();
userLoginVO.setId(user.getId());
userLoginVO.setOpenid(user.getOpenid());
userLoginVO.setToken(token);
return Result.success(userLoginVO);
}
}
- UserService
public interface UserService {
User login(UserLoginDTO userLoginDTO);
}
- UserServiceImpl,在service实现类中我们要先通过HttpClient获得openid,接着就是做一些判断新增user之类的操作
@Service
public class UserServiceImpl implements UserService {
//微信接口服务地址
public final static String WX_LOGIN="https://api.weixin.qq.com/sns/jscode2session";
@Autowired
private UserMapper userMapper;
@Autowired
private WeChatProperties weChatProperties;
@Override
public User login(UserLoginDTO userLoginDTO) {
//调用微信接口服务获得当前微信用户的openid
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);
//获取当前的openid
JSONObject jsonObject = JSON.parseObject(json);
String openid = jsonObject.getString("openid");
//判断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;
}
}
- UserMapper
@Mapper
public interface UserMapper {
@Select("select * from user where openid=#{openid}")
User getByOpenId(String openid);
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>
那么登录接口就开发好了,最后别忘了配置一下拦截器。
启动服务,在小程序端登录,出现以下则代表登录成功(http响应码为200):
菜品浏览
在课程中,该部分的代码是直接导入的,这里我还是从头来编码一遍。
许多方法在我们之前的接口已经开发好了,这里直接调用即可。
在菜品浏览功能开发,我们要涉及到四个接口的开发:
-
查询分类
-
根据分类id查询菜品(同时查询口味)
-
根据分类id查询套餐
-
根据套餐id查询包含的菜品
查询分类
一定要记得因为我们admin包下也有一个CategoryController,记得区分开
@RestController("userCategoryController")
@Slf4j
@Api(tags = "查询分类接口")
@RequestMapping("/user/category")
public class CategoryController {
@Autowired
private CategoryService categoryService;
@GetMapping("/list")
@ApiOperation("查询分类")
public Result<List<Category>> list(Integer type){
log.info("查询分类:{}",type);
List<Category> list=categoryService.list(type);
return Result.success(list);
}
}
其他层的代码我们之前就写好了,在controller层调用List()方法即可
根据分类id查询菜品同时查询口味
同样记得与admin包下的DishController区分开
@RestController("userDishController")
@Slf4j
@RequestMapping("/user/dish")
@Api(tags = "根据分类id查询菜品")
public class DishController {
@Autowired
private DishService dishService;
@GetMapping("/list")
@ApiOperation("根据分类id查询菜品")
public Result<List<DishVO>> list(Long categoryId){
log.info("根据分类id查询菜品:{}",categoryId);
Dish dish=new Dish();
dish.setCategoryId(categoryId);
dish.setStatus(StatusConstant.ENABLE);
List<DishVO> list=dishService.listWithFlavor(dish);
return Result.success(list);
}
}
service层代码就省略了,这里直接给出实现类代码:
//查询菜品以及其口味
@Override
public List<DishVO> listWithFlavor(Dish dish) {
List<Dish> list=dishMapper.list(dish);
List<DishVO> dishVOList=new ArrayList<>();
for (Dish dish1 : list) {
//将查询出来的dish的所以属性赋值给dishVO
DishVO dishVO=new DishVO();
BeanUtils.copyProperties(dish1,dishVO);
//查询口味数据
List<DishFlavor> flavors=dishMapper.getByDishId(dish1.getId());
dishVO.setFlavors(flavors);
dishVOList.add(dishVO);
}
return dishVOList;
}
根据分类id查询套餐、根据套餐id查询包含的菜品
@RestController("userSetmealController")
@Slf4j
@RequestMapping("/user/setmeal")
@Api(tags = "根据分类id查询套餐")
public class SetmealController {
@Autowired
private SetmealService setmealService;
@GetMapping("/list")
public Result<List<Setmeal>> list(Long categoryId){
log.info("根据分类id查询套餐:{}",categoryId);
Setmeal setmeal=new Setmeal();
setmeal.setCategoryId(categoryId);
setmeal.setStatus(StatusConstant.ENABLE);
List<Setmeal> list=setmealService.list(setmeal);
return Result.success(list);
}
@GetMapping("/dish/{id}")
@ApiOperation("根据套餐id查询菜品")
public Result<List<DishItemVO>> dishItemVO(@PathVariable Long id){
log.info("根据套餐id查询菜品:{}",id);
List<DishItemVO> list=setmealService.dishItemVO(id);
return Result.success(list);
}
}
SetmealServiceImpl实现类:
@Override
public List<Setmeal> list(Setmeal setmeal) {
List<Setmeal> list=setmealMapper.getByCategoryId(setmeal);
return list;
}
@Override
public List<DishItemVO> dishItemVO(Long id) {
//根据套餐id查询套餐中包含的菜品
List<DishItemVO> list=setmealMapper.getDishItemVO(id);
return list;
}
SetmealMapper:
因为DishItemVO中的属性在setmeal_dish和dish这两张表中,所以在这多表查询
List<Setmeal> getByCategoryId(Setmeal setmeal);
@Select("select dish.name,sd.copies,dish.image,dish.description from setmeal_dish sd left join dish on sd.dish_id=dish.id where sd.setmeal_id=#{id}")
List<DishItemVO> getDishItemVO(Long id);
SetmealMapper.xml:
<select id="getByCategoryId" resultType="com.sky.entity.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>