微信开发工具的下载地址: 微信开发者工具(稳定版 Stable Build)下载地址与更新日志 | 微信开放文档
2 .设置合法域名
3 .微信登录用户信息获取
如果要求登录后,无法显示用户信息
则应该修改一下【调试基础库】
4 .获取用户登录的openid
index/index.wxml
<view>
<button type="warn" bindtap="wxLogin">微信登录</button>
授权码:{{code}}
</view>
index/index.js
Page({
//微信登录,获取微信用户的授权码:openId【每一次都不一样】
wxLogin(){
wx.login({
success:(res)=>{
console.log(res.code);
this.setData({
code:res.code
})
}
})
},
})
5 .向后端发送请求
index/index.wxml
<view>
<button bindtap="sendRequest" type="default">发送请求</button>
</view>
index/index.js
//发送请求
sendRequest(){
wx.request({
url: 'http://localhost:8080/user/shop/status',
method:'GET',
success:(res)=>{
//res返回的是整个JSON对象
console.log(res.data);
}
})
}
6.微信登录
参考地址:小程序登录 / 小程序登录 (qq.com)
注意:可以使用postman测试登录凭证校验接口
需求分析
代码开发
配置微信登录所需配置项:
配置为微信用户生成jwt令牌时使用的配置项:
DTO设计
VO设计
根据接口定义创建UserController的login方法:
@RestController
@RequestMapping("/user/user")
@Api(tags = "C端用户相关接口")
@Slf4j
public class UserController {
@Autowired
private UserService userService;
@Autowired
private JwtProperties jwtProperties;
@PostMapping("/login")
@ApiOperation("微信登录")
public Result<UserLoginVO> login(@RequestBody UserLoginDTO userLoginDTO) {
//getCode:返回授权码
log.info("微信用户登录:{}",userLoginDTO.getCode());
//因为生成JWT令牌需要用户信息,所以要返回用户信息
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.getAdminTtl(),claims);
//userLogin是要返回给前端的数据
//这里是对返回给前端的数据进行封装
//因为这里要getId,所以在userMapper的映射文件中要指定主键值
UserLoginVO userLoginVO = UserLoginVO.builder()
.id(user.getId())
.openid(user.getOpenid())
.token(token)
.build();
return Result.success(userLoginVO);
}
}
创建UserService接口:
@Service
public interface UserService {
/**
* 微信登录
* @param userLoginDTO
* @return
*/
User wxLogin(UserLoginDTO userLoginDTO);
}
创建UserServiceImpl实现类:
@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) {
//调用微信接口服务,获取微信用户的openid
String code = userLoginDTO.getCode();
String openid = getOpenid(code);
//判断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,Object> map=new HashMap<>();
map.put("appid",weChatProperties.getAppid());
map.put("secret",weChatProperties.getSecret());
map.put("js_code",code);
map.put("grant_type","authorization_code");
//使用HttpClient发起GET请求
//doGet(请求地址,请求参数)
//返回的是一个JSON对象
String json = HttpClientUtil.doGet(WX_LOGIN, map);
JSONObject jsonObject = JSON.parseObject(json);
String openid = jsonObject.getString("openid");
return openid;
}
}
创建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" >
<!--useGeneratedKeys="true" keyProperty="id"
返回主键值
-->
<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校验
JwtTokenUserInterceptor
@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);
//将用户id存入JWT中
BaseContext.setCurrentId(userId);
//3、通过,放行
return true;
} catch (Exception ex) {
//4、不通过,响应401状态码
response.setStatus(401);
return false;
}
}
}
在WebMvcConfiguration配置类中注册拦截器:
@Autowired
private JwtTokenAdminInterceptor jwtTokenAdminInterceptor;
@Autowired
private JwtTokenUserInterceptor jwtTokenUserInterceptor;
/**
* 注册自定义拦截器
*
* @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");
}
7.用户下单
Day08-05-用户下单_需求分析和设计_接口设计_哔哩哔哩_bilibili
代码开发
根据用户下单接口的参数设计DTO
根据用户下单接口的返回结果设计VO:
创建OrderController并提供用户下单方法
//因为admin管理端也有一个userOrderController,所以这里要区分开
@RestController(value = "userOrderController")
@RequestMapping("/user/order")
@Api(tags = "用户端订单相关接口")
@Slf4j
public class OrderController {
@Autowired
private OrderService orderService;
/**
* 用户下单
*
* @param ordersSubmitDTO
* @return
*/
@PostMapping("/submit")
@ApiOperation("c端用户下单")
public Result<OrderSubmitVO> submit(@RequestBody OrdersSubmitDTO ordersSubmitDTO) {
log.info("用户下单:{}",ordersSubmitDTO);
OrderSubmitVO orderSubmitVO = orderService.submitOrder(ordersSubmitDTO);
return Result.success(orderSubmitVO);
}
}
创建OrderService接口,并声明用户下单方法
public interface OrderService {
/**
* 用户下单
* @param ordersSubmitDTO
* @return
*/
OrderSubmitVO submitOrder(OrdersSubmitDTO ordersSubmitDTO);
}
创建OrderServiceImpl实现OrderService接口
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private OrderDetailMapper orderDetailMapper;
@Autowired
private AddressBookMapper addressBookMapper;
@Autowired
private ShoppingCartMapper shoppingCartMapper;
@Autowired
private WeChatPayUtil weChatPayUtil;
@Autowired
private UserMapper userMapper;
/**
* 用户下单
* @param ordersSubmitDTO
* @return
*/
@Transactional//开启事务
@Override
public OrderSubmitVO submitOrder(OrdersSubmitDTO ordersSubmitDTO) {
//1. 出来各种业务异常(地址为空,购物车数据为空)
AddressBook addressBook = addressBookMapper.getById(ordersSubmitDTO.getAddressBookId());
if(addressBook==null){
//抛出业务异常
throw new AddressBookBusinessException(MessageConstant.ADDRESS_BOOK_IS_NULL);
}
//查询当前用户的购物车数据
Long userId = BaseContext.getCurrentId();
//因为shoppingCartMapper的list要求传入是ShoppingCart对象,所以我们要先转换为该对象
ShoppingCart shoppingCart = new ShoppingCart();
shoppingCart.setUserId(userId);
List<ShoppingCart> shoppingCartList = shoppingCartMapper.list(shoppingCart);
if(shoppingCartList==null || shoppingCartList.size()==0){
//抛出业务异常
throw new ShoppingCartBusinessException(MessageConstant.SHOPPING_CART_IS_NULL);
}
//2.向订单表插入1条数据
Orders orders = new Orders();
//属性拷贝,将已经传入的数据拷贝到另外一个进行查询
BeanUtils.copyProperties(ordersSubmitDTO, orders);
orders.setOrderTime(LocalDateTime.now());
orders.setPayStatus(Orders.UN_PAID);//未支付
orders.setStatus(Orders.PENDING_PAYMENT);//待付款
orders.setNumber(String.valueOf(System.currentTimeMillis()));//使用时间戳形成订单号
orders.setPhone(addressBook.getPhone());
orders.setConsignee(addressBook.getConsignee());
orders.setUserId(userId);
orderMapper.insert(orders);
//生成一个List集合存放数据
ArrayList<Object> orderDetailList = new ArrayList<>();
//3.向订单明细表插入n条数据【需要获取当前的订单id】,故上面的insert中要返回主键值
for(ShoppingCart cart:shoppingCartList){
OrderDetail orderDetail = new OrderDetail();//订单明细
BeanUtils.copyProperties(cart, orderDetail);
orderDetail.setOrderId(orders.getId());//设置当前订单详细关联的订单id
orderDetailList.add(orderDetail);
}
orderDetailMapper.insertBatch(orderDetailList);
//4.清空当前用户的购物车数据
shoppingCartMapper.deleteById(userId);
//5.封装VO返回结果
OrderSubmitVO orderSubmitVO = OrderSubmitVO.builder()
.id(orders.getId())
.orderTime(orders.getOrderTime())
.orderNumber(orders.getNumber())
.orderAmount(orders.getAmount())
.build();
return orderSubmitVO;
}
}
创建OrderMapper接口和对应的xml映射文件:
创建OrderDetailMapper接口和对应的xml映射文件
8.订单支付
微信支付介绍
参考:产品中心 - 微信支付商户平台
JSAPI下单:商户系统调用该接口在微信支付服务后台生成预支付交易单
微信小程序调起支付:通过JSAPI下单接口获取到发起支付的必要参数prepay_id,然后使用微信支付提供的小程序方法调起小程序支付
微信小程序支付时序图
获取临时域名:支付成功后微信服务通过该域名回调我们的程序
代码导入
微信支付相关配置:
重点代码
OrderController
/**
* 订单支付
*
* @param ordersPaymentDTO
* @return
*/
@PutMapping("/payment")
@ApiOperation("订单支付")
public Result<OrderPaymentVO> payment(@RequestBody OrdersPaymentDTO ordersPaymentDTO) throws Exception {
log.info("订单支付:{}", ordersPaymentDTO);
OrderPaymentVO orderPaymentVO = orderService.payment(ordersPaymentDTO);
log.info("生成预支付交易单:{}", orderPaymentVO);
//业务出来,修改订单状态,来单提醒
//因为无法调用微信支付接口,所以模拟一下
orderService.paySuccess(ordersPaymentDTO.getOrderNumber());
return Result.success(orderPaymentVO);
}
OrderService
/**
* 订单支付
* @param ordersPaymentDTO
* @return
*/
OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception;
/**
* 支付成功,修改订单状态
* @param outTradeNo
*/
void paySuccess(String outTradeNo);
OrderServiceImpl
/**
* 订单支付
*
* @param ordersPaymentDTO
* @return
*/
public OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception {
// 当前登录用户id
Long userId = BaseContext.getCurrentId();
User user = userMapper.getById(userId);
//调用微信支付接口,生成预支付交易单
// JSONObject jsonObject = weChatPayUtil.pay(
// ordersPaymentDTO.getOrderNumber(), //商户订单号
// new BigDecimal(0.01), //支付金额,单位 元
// "苍穹外卖订单", //商品描述
// user.getOpenid() //微信用户的openid
// );
//因为无法调用微信支付接口,所以模拟一下
JSONObject jsonObject = new JSONObject();
if (jsonObject.getString("code") != null && jsonObject.getString("code").equals("ORDERPAID")) {
throw new OrderBusinessException("该订单已支付");
}
OrderPaymentVO vo = jsonObject.toJavaObject(OrderPaymentVO.class);
vo.setPackageStr(jsonObject.getString("package"));
return vo;
}
/**
* 支付成功,修改订单状态
*
* @param outTradeNo
*/
public void paySuccess(String outTradeNo) {
// 根据订单号查询订单
Orders ordersDB = orderMapper.getByNumber(outTradeNo);
// 根据订单id更新订单的状态、支付方式、支付状态、结账时间
Orders orders = Orders.builder()
.id(ordersDB.getId())
.status(Orders.TO_BE_CONFIRMED)
.payStatus(Orders.PAID)
.checkoutTime(LocalDateTime.now())
.build();
orderMapper.update(orders);
}
OrderMapper
/**
* 修改订单信息
* @param orders
*/
void update(Orders orders);
OrderMapper
<update id="update" parameterType="com.sky.entity.Orders">
update orders
<set>
<if test="cancelReason != null and cancelReason!='' ">
cancel_reason=#{cancelReason},
</if>
<if test="rejectionReason != null and rejectionReason!='' ">
rejection_reason=#{rejectionReason},
</if>
<if test="cancelTime != null">
cancel_time=#{cancelTime},
</if>
<if test="payStatus != null">
pay_status=#{payStatus},
</if>
<if test="payMethod != null">
pay_method=#{payMethod},
</if>
<if test="checkoutTime != null">
checkout_time=#{checkoutTime},
</if>
<if test="status != null">
status = #{status},
</if>
<if test="deliveryTime != null">
delivery_time = #{deliveryTime}
</if>
</set>
where id = #{id}
</update>
PayNotifyController【固定】
/**
* 支付回调相关接口
*/
@RestController
@RequestMapping("/notify")
@Slf4j
public class PayNotifyController {
@Autowired
private OrderService orderService;
@Autowired
private WeChatProperties weChatProperties;
/**
* 支付成功回调
*
* @param request
*/
@RequestMapping("/paySuccess")
public void paySuccessNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
//读取数据
String body = readData(request);
log.info("支付成功回调:{}", body);
//数据解密
String plainText = decryptData(body);
log.info("解密后的文本:{}", plainText);
JSONObject jsonObject = JSON.parseObject(plainText);
String outTradeNo = jsonObject.getString("out_trade_no");//商户平台订单号
String transactionId = jsonObject.getString("transaction_id");//微信支付交易号
log.info("商户平台订单号:{}", outTradeNo);
log.info("微信支付交易号:{}", transactionId);
//业务处理,修改订单状态、来单提醒
orderService.paySuccess(outTradeNo);
//给微信响应
responseToWeixin(response);
}
/**
* 读取数据
*
* @param request
* @return
* @throws Exception
*/
private String readData(HttpServletRequest request) throws Exception {
BufferedReader reader = request.getReader();
StringBuilder result = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
if (result.length() > 0) {
result.append("\n");
}
result.append(line);
}
return result.toString();
}
/**
* 数据解密
*
* @param body
* @return
* @throws Exception
*/
private String decryptData(String body) throws Exception {
JSONObject resultObject = JSON.parseObject(body);
JSONObject resource = resultObject.getJSONObject("resource");
String ciphertext = resource.getString("ciphertext");
String nonce = resource.getString("nonce");
String associatedData = resource.getString("associated_data");
AesUtil aesUtil = new AesUtil(weChatProperties.getApiV3Key().getBytes(StandardCharsets.UTF_8));
//密文解密
String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
nonce.getBytes(StandardCharsets.UTF_8),
ciphertext);
return plainText;
}
/**
* 给微信响应
* @param response
*/
private void responseToWeixin(HttpServletResponse response) throws Exception{
response.setStatus(200);
HashMap<Object, Object> map = new HashMap<>();
map.put("code", "SUCCESS");
map.put("message", "SUCCESS");
response.setHeader("Content-type", ContentType.APPLICATION_JSON.toString());
response.getOutputStream().write(JSONUtils.toJSONString(map).getBytes(StandardCharsets.UTF_8));
response.flushBuffer();
}
}