学习目标

  • 登录页的配置
  • 登录成功跳转实现
  • 结算页查询实现
  • 下单实现
  • 变更库存
  • 增加积分
  • 支付流程介绍
  • 微信扫码支付介绍

    1 登录页面配置

    前面使用的都是采用Postman实现登录,接着我们实现一次oauth自定义登录。

    1.1 准备工作

    (1)静态资源导入
    资料/页面/前端登录相关的静态资源导入到changgou-user-oauth中,如下图。
    第11章 订单 - 图1
    并替换掉./ 为/ 如图 按CTR+R 并在第一个输入框输入 ./ 再第二个输入框输入/ 并点击替换所有。
    第11章 订单 - 图2
    (2)引入thymeleaf
    修改changgou-user-oauth,引入thymeleaf模板引擎起步依赖
    1. <!--thymeleaf-->
    2. <dependency>
    3. <groupId>org.springframework.boot</groupId>
    4. <artifactId>spring-boot-starter-thymeleaf</artifactId>
    5. </dependency>
    (3)登录配置
    修改changgou-user-oauth,编写一个控制器com.changgou.oauth.controller.LoginRedirect,实现登录页跳转,代码如下:
    1. @Controller
    2. @RequestMapping(value = "/oauth")
    3. public class LoginRedirect {
    4. /***
    5. * 跳转到登录页面
    6. * @return
    7. */
    8. @GetMapping(value = "/login")
    9. public String login(){
    10. return "login";
    11. }
    12. }
    (4)登录页配置
    针对静态资源和登录页面,我们需要实现忽略安全配置,并且要指定登录页面,修改com.changgou.oauth.config.MyWebSecurityConfigconfigure方法,代码如下:
    第11章 订单 - 图3
    测试:浏览器输入http://localhost:9001/oauth/login
    第11章 订单 - 图4

    1.2 登录实现

    点击登录按钮,访问之前的登录方法实现登录,我们需要对登录页做一下调整。
    (1)引入thymeleaf命名空间
    修改login.html,引入命名空间
    第11章 订单 - 图5
    (2)登录脚本
    点击登录按钮,使用vue+axios实现登录,我们需要定义脚本访问后台登录方法。
    先添加vue入口标签:修改login.html,在73行左右的标签上添加id=”app”,代码如下:
    第11章 订单 - 图6
    引入js
    1. <script src="/js/vue.js"></script>
    2. <script src="/js/axios.js"></script>
    登录脚本实现:
    第11章 订单 - 图7
    (3)表单修改
    第11章 订单 - 图8
    (4)配置config1:
    第11章 订单 - 图9
    修改config2
    第11章 订单 - 图10
    (5)测试
    第11章 订单 - 图11

    1.3 登录跳转

    用户没有登录的时候,我们直接访问购物车,效果如下:
    第11章 订单 - 图12
    我们可以发现,返回的只是个错误状态码,不方便测试,我们可以重定向到登录页面,让用户登录,我们可以修改网关的头文件,让用户每次没登录的时候,都跳转到登录页面。
    修改changgou-gateway-web的com.changgou.filter.AuthorizeFilter,代码如下:
    第11章 订单 - 图13
    第11章 订单 - 图14
    代码如下:
    1. @Component
    2. public class AuthorizeFilter implements GlobalFilter, Ordered {
    3. private static final String AUTHORIZE_TOKEN = "Authorization";
    4. private static final String LOGIN_URL="http://localhost:9001/oauth/login";
    5. @Override
    6. public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    7. //需要校验令牌的信息是否合法
    8. //1.获取请求对象 request
    9. ServerHttpRequest request = exchange.getRequest();
    10. //2.获取响应对象 response
    11. ServerHttpResponse response = exchange.getResponse();
    12. //3.判断请求的路径是否是登录请求,如果是登录请求,放行请求
    13. String path = request.getURI().getPath();//路径
    14. if (path.startsWith("/api/user/login")) {
    15. //放行
    16. return chain.filter(exchange);
    17. }
    18. //4.判断请求不是登录请求,就要校验,
    19. //4.1.先从header信息中获取令牌信息 如果没有 头名为AUTHORIZE_TOKEN的值
    20. String token = request.getHeaders().getFirst(AUTHORIZE_TOKEN);
    21. if (StringUtils.isEmpty(token)) {
    22. //4.2 再去从请求参数中获取令牌新 如果没有
    23. token = request.getQueryParams().getFirst(AUTHORIZE_TOKEN);
    24. }
    25. if(StringUtils.isEmpty(token)) {
    26. //4.3 再从cookie中获取令牌信息 如果也没有 说明无权限 直接返回
    27. HttpCookie httpCookie = request.getCookies().getFirst(AUTHORIZE_TOKEN);
    28. if(httpCookie!=null){
    29. token = httpCookie.getValue();//
    30. }
    31. }
    32. if(StringUtils.isEmpty(token)){
    33. //没有令牌 说明无权限 直接返回
    34. response.getHeaders().set("Location",LOGIN_URL+"?FROM="+request.getURI().toString());
    35. response.setStatusCode(HttpStatus.SEE_OTHER);
    36. return response.setComplete();//请求完成
    37. }
    38. //5 如果有令牌信息 就要校验令牌的合法性,校验通过放行 不通过就直接返回
    39. /*try {
    40. JwtUtil.parseJWT(token);
    41. } catch (Exception e) {
    42. e.printStackTrace();
    43. //没有令牌 说明无权限 直接返回
    44. System.out.println("解析失败");
    45. response.setStatusCode(HttpStatus.UNAUTHORIZED);
    46. return response.setComplete();//请求完成
    47. }*/
    48. //放行
    49. return chain.filter(exchange);
    50. }
    51. @Override
    52. public int getOrder() {
    53. return 0;
    54. }
    55. }
    此时再测试,就可以跳转到登录页面了。当然,在工作中,这里不能直接跳转到登录页,应该提示状态给页面,让页面根据判断跳转,这里只是为了方便测试,如下图所示:
    第11章 订单 - 图15

    1.4 成功登录跳转到原访问页

    第11章 订单 - 图16
    上面虽然实现了登录跳转,但登录成功后却并没有返回到要访问的购物车页面,我们可以将用户要访问的页面作为参数传递给登录控制器,登录控制器记录下来,每次登录成功后,再跳转记录访问路劲参数指定的页面即可。
    (1)认证服务器获取FROM参数
    修改changgou-user-oauth的com.changgou.oauth.controller.LoginRedirect记录访问来源页,代码如下:
    第11章 订单 - 图17
    修改页面,获取来源页信息,并存到from变量中,登录成功后跳转到该地址。
    第11章 订单 - 图18
    此时再测试,就可以识别未登录用户,跳转到登录页,然后根据登录状态,如果登录成功,则跳转到来源页。

    2 订单结页

    2.1 收件地址分析

    用户从购物车页面点击结算,跳转到订单结算页,结算页需要加载用户对应的收件地址,如下图:
    第11章 订单 - 图19
    表结构分析:
    1. CREATE TABLE `tb_address` (
    2. `id` int(11) NOT NULL AUTO_INCREMENT,
    3. `username` varchar(50) DEFAULT NULL COMMENT '用户名',
    4. `provinceid` varchar(20) DEFAULT NULL COMMENT '省',
    5. `cityid` varchar(20) DEFAULT NULL COMMENT '市',
    6. `areaid` varchar(20) DEFAULT NULL COMMENT '县/区',
    7. `phone` varchar(20) DEFAULT NULL COMMENT '电话',
    8. `address` varchar(200) DEFAULT NULL COMMENT '详细地址',
    9. `contact` varchar(50) DEFAULT NULL COMMENT '联系人',
    10. `is_default` varchar(1) DEFAULT NULL COMMENT '是否是默认 1默认 0否',
    11. `alias` varchar(50) DEFAULT NULL COMMENT '别名',
    12. PRIMARY KEY (`id`)
    13. ) ENGINE=InnoDB AUTO_INCREMENT=66 DEFAULT CHARSET=utf8;
    我们可以根据用户登录名去tb_address表中查询对应的数据。

    2.2 实现用户收件地址查询

    2.2.1 代码实现

    (1)业务层
    业务层接口
    修改changgou-service-user微服务,需改com.changgou.user.service.AddressService接口,添加根据用户名字查询用户收件地址信息,代码如下:
    1. /***
    2. * 收件地址查询
    3. * @param username
    4. * @return
    5. */
    6. List<Address> list(String username);
    业务层接口实现类
    修改changgou-service-user微服务,修改com.changgou.user.service.impl.AddressServiceImpl类,添加根据用户查询用户收件地址信息实现方法,如下代码:
    1. /***
    2. * 收件地址查询
    3. * @param username
    4. * @return
    5. */
    6. @Override
    7. public List<Address> list(String username) {
    8. Address address = new Address();
    9. address.setUsername(username);
    10. return addressMapper.select(address);
    11. }
    (2)控制层
    修改changgou-service-user微服务,修改com.changgou.user.controller.AddressController,添加根据用户名查询用户收件信息方法,代码如下:
    1. /****
    2. * 用户收件地址
    3. */
    4. @GetMapping(value = "/user/list")
    5. public Result<List<Address>> list(){
    6. //获取用户登录信息
    7. Map<String, String> userMap = TokenDecode.getUserInfo();
    8. String username = userMap.get("username");
    9. //查询用户收件地址
    10. List<Address> addressList = addressService.list(username);
    11. return new Result(true, StatusCode.OK,"查询成功!",addressList);
    12. }

    2.2.2 测试

    访问 http://localhost:8001/api/address/user/list
    第11章 订单 - 图20

    2.2.3 运送清单

    第11章 订单 - 图21
    运送清单其实就是购物车列表,直接查询之前的购物车列表即可,这里不做说明了。

    3 下单

    3.1 业务分析

    点击结算页的时候,会立即创建订单数据,创建订单数据会将数据存入到2张表中,分别是订单表和订单明细表,此处还需要修改商品对应的库存数量。
    第11章 订单 - 图22
    订单表结构如下:
    1. CREATE TABLE `tb_order` (
    2. `id` varchar(50) COLLATE utf8_bin NOT NULL COMMENT '订单id',
    3. `total_num` int(11) DEFAULT NULL COMMENT '数量合计',
    4. `total_money` int(11) DEFAULT NULL COMMENT '金额合计',
    5. `pre_money` int(11) DEFAULT NULL COMMENT '优惠金额',
    6. `post_fee` int(11) DEFAULT NULL COMMENT '邮费',
    7. `pay_money` int(11) DEFAULT NULL COMMENT '实付金额',
    8. `pay_type` varchar(1) COLLATE utf8_bin DEFAULT NULL COMMENT '支付类型,1、在线支付、0 货到付款',
    9. `create_time` datetime DEFAULT NULL COMMENT '订单创建时间',
    10. `update_time` datetime DEFAULT NULL COMMENT '订单更新时间',
    11. `pay_time` datetime DEFAULT NULL COMMENT '付款时间',
    12. `consign_time` datetime DEFAULT NULL COMMENT '发货时间',
    13. `end_time` datetime DEFAULT NULL COMMENT '交易完成时间',
    14. `close_time` datetime DEFAULT NULL COMMENT '交易关闭时间',
    15. `shipping_name` varchar(20) COLLATE utf8_bin DEFAULT NULL COMMENT '物流名称',
    16. `shipping_code` varchar(20) COLLATE utf8_bin DEFAULT NULL COMMENT '物流单号',
    17. `username` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT '用户名称',
    18. `buyer_message` varchar(1000) COLLATE utf8_bin DEFAULT NULL COMMENT '买家留言',
    19. `buyer_rate` char(1) COLLATE utf8_bin DEFAULT NULL COMMENT '是否评价',
    20. `receiver_contact` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT '收货人',
    21. `receiver_mobile` varchar(12) COLLATE utf8_bin DEFAULT NULL COMMENT '收货人手机',
    22. `receiver_address` varchar(200) COLLATE utf8_bin DEFAULT NULL COMMENT '收货人地址',
    23. `source_type` char(1) COLLATE utf8_bin DEFAULT NULL COMMENT '订单来源:1:web,2:app,3:微信公众号,4:微信小程序 5 H5手机页面',
    24. `transaction_id` varchar(30) COLLATE utf8_bin DEFAULT NULL COMMENT '交易流水号',
    25. `order_status` char(1) COLLATE utf8_bin DEFAULT NULL COMMENT '订单状态,0:未完成,1:已完成,2:已退货',
    26. `pay_status` char(1) COLLATE utf8_bin DEFAULT NULL COMMENT '支付状态,0:未支付,1:已支付,2:支付失败',
    27. `consign_status` char(1) COLLATE utf8_bin DEFAULT NULL COMMENT '发货状态,0:未发货,1:已发货,2:已收货',
    28. `is_delete` char(1) COLLATE utf8_bin DEFAULT NULL COMMENT '是否删除',
    29. PRIMARY KEY (`id`),
    30. KEY `create_time` (`create_time`),
    31. KEY `status` (`order_status`),
    32. KEY `payment_type` (`pay_type`)
    33. ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
    订单明细表结构如下:
    1. CREATE TABLE `tb_order_item` (
    2. `id` varchar(50) COLLATE utf8_bin NOT NULL COMMENT 'ID',
    3. `category_id1` int(11) DEFAULT NULL COMMENT '1级分类',
    4. `category_id2` int(11) DEFAULT NULL COMMENT '2级分类',
    5. `category_id3` int(11) DEFAULT NULL COMMENT '3级分类',
    6. `spu_id` varchar(20) COLLATE utf8_bin DEFAULT NULL COMMENT 'SPU_ID',
    7. `sku_id` bigint(20) NOT NULL COMMENT 'SKU_ID',
    8. `order_id` bigint(20) NOT NULL COMMENT '订单ID',
    9. `name` varchar(200) COLLATE utf8_bin DEFAULT NULL COMMENT '商品名称',
    10. `price` int(20) DEFAULT NULL COMMENT '单价',
    11. `num` int(10) DEFAULT NULL COMMENT '数量',
    12. `money` int(20) DEFAULT NULL COMMENT '总金额',
    13. `pay_money` int(11) DEFAULT NULL COMMENT '实付金额',
    14. `image` varchar(200) COLLATE utf8_bin DEFAULT NULL COMMENT '图片地址',
    15. `weight` int(11) DEFAULT NULL COMMENT '重量',
    16. `post_fee` int(11) DEFAULT NULL COMMENT '运费',
    17. `is_return` char(1) COLLATE utf8_bin DEFAULT NULL COMMENT '是否退货,0:未退货,1:已退货',
    18. PRIMARY KEY (`id`),
    19. KEY `item_id` (`sku_id`),
    20. KEY `order_id` (`order_id`)
    21. ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

    3.2 下单实现

    下单的时候,先添加订单往tb_order表中增加数据,再添加订单明细,往tb_order_item表中增加数据。

    3.2.1 代码实现

    这里先修改changgou-service-order微服务,实现下单操作,这里会生成订单号,我们首先需要在启动类中创建一个IdWorker对象。
    com.changgou.OrderApplication中创建IdWorker,代码如下:
    1. @Bean
    2. public IdWorker idWorker(){
    3. return new IdWorker(1,1);
    4. }
    (1)业务层
    修改changgou-service-order微服务,修改com.changgou.order.service.impl.OrderServiceImpl,代码如下:
    修改订单微服务添加com.changgou.order.service.impl.OrderServiceImpl,代码如下:
    1. @Service
    2. public class OrderServiceImpl implements OrderService {
    3. @Autowired
    4. private OrderMapper orderMapper;
    5. @Autowired
    6. private OrderItemMapper orderItemMapper;
    7. @Autowired
    8. private CartService cartService;
    9. @Autowired
    10. private IdWorker idWorker;
    11. @Autowired
    12. private RedisTemplate redisTemplate;
    13. /***
    14. * 添加订单
    15. * @param order
    16. * @return
    17. */
    18. @Override
    19. public int add(Order order) {
    20. //查询出用户的所有购物车
    21. List<OrderItem> orderItems = cartService.list(order.getUsername());
    22. //统计计算
    23. int totalMoney = 0;
    24. int totalPayMoney=0;
    25. int num = 0;
    26. for (OrderItem orderItem : orderItems) {
    27. //总金额
    28. totalMoney+=orderItem.getMoney();
    29. //实际支付金额
    30. totalPayMoney+=orderItem.getPayMoney();
    31. //总数量
    32. num+=orderItem.getNum();
    33. }
    34. order.setTotalNum(num);
    35. order.setTotalMoney(totalMoney);
    36. order.setPayMoney(totalPayMoney);
    37. order.setPreMoney(totalMoney-totalPayMoney);
    38. //其他数据完善
    39. order.setCreateTime(new Date());
    40. order.setUpdateTime(order.getCreateTime());
    41. order.setBuyerRate("0"); //0:未评价,1:已评价
    42. order.setSourceType("1"); //来源,1:WEB
    43. order.setOrderStatus("0"); //0:未完成,1:已完成,2:已退货
    44. order.setPayStatus("0"); //0:未支付,1:已支付,2:支付失败
    45. order.setConsignStatus("0"); //0:未发货,1:已发货,2:已收货
    46. order.setId("NO."+idWorker.nextId());
    47. int count = orderMapper.insertSelective(order);
    48. //添加订单明细
    49. for (OrderItem orderItem : orderItems) {
    50. orderItem.setId("NO."+idWorker.nextId());
    51. orderItem.setIsReturn("0");
    52. orderItem.setOrderId(order.getId());
    53. orderItemMapper.insertSelective(orderItem);
    54. }
    55. //清除Redis缓存购物车数据
    56. redisTemplate.delete("Cart_"+order.getUsername());
    57. return count;
    58. }
    59. }
    (2)控制层
    修改changgou-service-order微服务,修改com.changgou.order.controller.OrderController类,代码如下:
    1. @Autowired
    2. private TokenDecode tokenDecode;
    3. /**
    4. * 添加订单 1.添加订单 2.更新库存 3 添加积分 4.清空购物车
    5. * @param
    6. * @return
    7. */
    8. @PostMapping("/add")
    9. public Result add(@RequestBody Order order) {
    10. String username = tokenDecode.getUsername();
    11. order.setUsername(username);
    12. orderService.add(order);
    13. return new Result(true, StatusCode.OK,"添加订单成功");
    14. }
    注意:获取用户名的方式,可以参考之前的方式:直接在微服务中创建javabean即可,如下图所示:
    第11章 订单 - 图23

    3.2.2 测试

    保存订单测试,表数据变化如下:
    tb_order表数据:
    第11章 订单 - 图24
    tb_order_item表数据:
    第11章 订单 - 图25

    3.3 库存变更

    3.3.1 业务分析

    上面操作只实现了下单操作,但对应的库存还没跟着一起减少,我们在下单之后,应该调用商品微服务,将下单的商品库存减少,销量增加。每次订单微服务只需要将用户名传到商品微服务,商品微服务通过用户名到Redis中查询对应的购物车数据,然后执行库存减少,库存减少需要控制当前商品库存>=销售数量。
    第11章 订单 - 图26
    如何控制库存数量>=销售数量呢?其实可以通过SQL语句实现,每次减少数量的时候,加个条件判断。
    where num>=#{num}即可。
    该工程中一会儿需要查询购物车数据,所以需要引入订单的api,在pom.xml中添加如下依赖:
    1. <!--order api 依赖-->
    2. <dependency>
    3. <groupId>com.changgou</groupId>
    4. <artifactId>changgou-service-order-api</artifactId>
    5. <version>1.0-SNAPSHOT</version>
    6. </dependency>

    3.3.2 代码实现

    (1)Dao层
    修改changgou-service-goods微服务的com.changgou.goods.dao.SkuMapper接口,增加库存递减方法,代码如下:
    1. public interface SkuMapper extends Mapper<Sku> {
    2. @Update(value="update tb_sku set num=num-#{num} where id=#{id} and num>=#{num}")
    3. int decCount(@Param(value="id") Long id,@Param(value="num") Integer num);
    4. }
    (2)业务层
    修改changgou-service-goods微服务的com.changgou.goods.service.SkuService接口,添加如下方法:
    1. //扣减库存
    2. int decCount(Long id, Integer num);
    修改changgou-service-goods微服务的com.changgou.goods.service.impl.SkuServiceImpl实现类,添加一个实现方法,代码如下:
    1. @Override
    2. public int decCount(Long id, Integer num) {
    3. /* Sku sku = skuMapper.selectByPrimaryKey(id);
    4. sku.setNum(sku.getNum()-num);
    5. skuMapper.updateByPrimaryKeySelective(sku);*/
    6. return skuMapper.decCount(id,num);
    7. }
    (3)控制层
    修改changgou-service-goods的com.changgou.goods.controller.SkuController类,添加库存递减方法,代码如下:
    1. /**
    2. * 给指定的商品的ID 扣库存
    3. * @param id 要扣库存的商品的ID skuid
    4. * @param num 要扣的数量
    5. * @return
    6. */
    7. @GetMapping("/decCount")
    8. public Result decCount(@RequestParam(name="id") Long id, @RequestParam(name="num") Integer num){
    9. int count = skuService.decCount(id,num);
    10. if (count > 0) {
    11. return new Result<List<Sku>>(true, StatusCode.OK,"扣库存成功") ;
    12. }
    13. return new Result<List<Sku>>(false, StatusCode.ERROR,"扣库存失败") ;
    14. }
    (4)创建feign
    同时在changgou-service-goods-api工程添加com.changgou.goods.feign.SkuFeign的实现,代码如下:
    1. /***
    2. * 库存递减
    3. * @param username
    4. * @return
    5. */
    6. @GetMapping("/decCount")
    7. public Result decCount(@RequestParam(name="id") Long id, @RequestParam(name="num") Integer num);

    3.3.3 调用库存递减

    修改changgou-service-order微服务的com.changgou.order.service.impl.OrderServiceImpl类的add方法,增加库存递减的调用。
    先注入SkuFeign
    1. @Autowired
    2. private SkuFeign skuFeign;
    再调用库存递减方法
    1. //库存减库存
    2. skuFeign.decCount(skuId, orderItem.getNum());
    完整代码如下:
    第11章 订单 - 图27

    3.3.4 测试

    库存减少前,查询数据库Sku数据如下:个数98,销量0
    第11章 订单 - 图28
    使用Postman执行 http://localhost:18081/api/order/add
    第11章 订单 - 图29
    执行测试后,剩余库存97,销量1
    第11章 订单 - 图30

    3.4 增加积分

    比如每次下单完成之后,给用户增加10个积分,支付完成后赠送优惠券,优惠券可用于支付时再次抵扣。我们先完成增加积分功能。如下表:points表示用户积分
    1. CREATE TABLE `tb_user` (
    2. `username` varchar(50) NOT NULL COMMENT '用户名',
    3. `password` varchar(100) NOT NULL COMMENT '密码,加密存储',
    4. `phone` varchar(20) DEFAULT NULL COMMENT '注册手机号',
    5. `email` varchar(50) DEFAULT NULL COMMENT '注册邮箱',
    6. `created` datetime NOT NULL COMMENT '创建时间',
    7. `updated` datetime NOT NULL COMMENT '修改时间',
    8. `source_type` varchar(1) DEFAULT NULL COMMENT '会员来源:1:PC,2:H5,3:Android,4:IOS',
    9. `nick_name` varchar(50) DEFAULT NULL COMMENT '昵称',
    10. `name` varchar(50) DEFAULT NULL COMMENT '真实姓名',
    11. `status` varchar(1) DEFAULT NULL COMMENT '使用状态(1正常 0非正常)',
    12. `head_pic` varchar(150) DEFAULT NULL COMMENT '头像地址',
    13. `qq` varchar(20) DEFAULT NULL COMMENT 'QQ号码',
    14. `is_mobile_check` varchar(1) DEFAULT '0' COMMENT '手机是否验证 (0否 1是)',
    15. `is_email_check` varchar(1) DEFAULT '0' COMMENT '邮箱是否检测(0否 1是)',
    16. `sex` varchar(1) DEFAULT '1' COMMENT '性别,1男,0女',
    17. `user_level` int(11) DEFAULT NULL COMMENT '会员等级',
    18. `points` int(11) DEFAULT NULL COMMENT '积分',
    19. `experience_value` int(11) DEFAULT NULL COMMENT '经验值',
    20. `birthday` datetime DEFAULT NULL COMMENT '出生年月日',
    21. `last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间',
    22. PRIMARY KEY (`username`),
    23. UNIQUE KEY `username` (`username`) USING BTREE
    24. ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表';

    3.4.1 代码实现

    (1)dao层
    修改changgou-service-user微服务的com.changgou.user.dao.UserMapper接口,增加用户积分方法,代码如下:
    1. public interface UserMapper extends Mapper<User> {
    2. @Update(value="update tb_user set points=points+#{points} where username =#{username}")
    3. int addPoints(@Param(value="username") String username, @Param(value="points")Integer points);
    4. }
    (2)业务层
    修改changgou-service-user微服务的com.changgou.user.service.UserService接口,代码如下:
    1. int addPoints(String username, Integer points);
    修改changgou-service-user微服务的com.changgou.user.service.impl.UserServiceImpl,增加添加积分方法实现,代码如下:
    1. @Override
    2. public int addPoints(String username, Integer points) {
    3. // update tb_user set points=points+#{points} where username =#{username}
    4. return userMapper.addPoints(username,points);
    5. }
    (3)控制层
    修改changgou-service-user微服务的com.changgou.user.controller.UserController,添加增加用户积分方法,代码如下:
    1. @GetMapping("/points/add")
    2. public Result addPoints(@RequestParam(name="username") String username,
    3. @RequestParam(name="points") Integer points){
    4. int i = userService.addPoints(username, points);
    5. if(i>0){
    6. return new Result(true, StatusCode.OK, "添加积分成功");
    7. }else{
    8. return new Result(false, StatusCode.ERROR, "没有更新");
    9. }
    10. }
    (4)Feign添加
    修改changgou-service-user-api工程,修改com.changgou.user.feign.UserFeign,添加增加用户积分方法,代码如下:
    1. /**
    2. * 给指定的用户名 添加积分
    3. * @param username 用户名
    4. * @param points 积分数
    5. * @return
    6. */
    7. @GetMapping("/points/add")
    8. public Result addPoints(@RequestParam(name="username") String username,
    9. @RequestParam(name="points") Integer points);

    3.4.2 增加积分调用

    修改changgou-service-order,添加changgou-service-user-api的依赖,修改pom.xml,添加如下依赖:
    1. <!--user api 依赖-->
    2. <dependency>
    3. <groupId>com.changgou</groupId>
    4. <artifactId>changgou-service-user-api</artifactId>
    5. <version>1.0-SNAPSHOT</version>
    6. </dependency>
    在增加订单的时候,同时添加用户积分,修改changgou-service-order微服务的com.changgou.order.service.impl.OrderServiceImpl下单方法,增加调用添加积分方法,代码如下:
    1. userFeign.addPoints(order.getUsername(), 10);
    整体代码如下: ```java @Autowired private UserFeign userFeign; public int add(Order order) {
    1. //略
    2. //3.添加用户的积分 默认加 10分
    3. userFeign.addPoints(order.getUsername(), 10);
    4. //略

}

  1. 修改changgou-service-order的启动类`com.changgou.OrderApplication`,添加feign的包路径:<br />![](https://cdn.nlark.com/yuque/0/2021/png/12589476/1636275654863-0e220006-36c8-4d21-85df-1cda310f85a0.png#)
  2. <a name="48ce55ca"></a>
  3. ### 3.5 测试
  4. 整体代码:

@Override @Transactional(rollbackFor = Exception.class)//本地的mysql的事务 !!!!!分布式事务的问题 public void add(Order order) { //1.添加数据到订单表 //1.1 设置主键 String orderId = idWorker.nextId()+””; order.setId(orderId); //1.2 设置总金额 总数量 todo 获取redis中的购物车的数据 循环遍历获取到总金额和总数量 Integer totalNum=0; Integer totalMoney=0; List values = redisTemplate.boundHashOps(“Cart“ + order.getUsername()).values(); for (OrderItem orderItem : values) { Integer num = orderItem.getNum(); Integer money = orderItem.getMoney(); totalNum+=num; totalMoney+=money; //补充属性 String orderItemId = idWorker.nextId() + “”; orderItem.setId(orderItemId); orderItem.setOrderId(orderId); orderItem.setIsReturn(“0”);//未退货 orderItemMapper.insertSelective(orderItem); //减库存 skuFeign.decCount(orderItem.getSkuId(),num);//todo 优化 } //减库存 update tb_sku set num=num-#{num} where id=#{id} and num>=#{num} //1.在changgou-service-goods-api 创建feign接口 写一个方法 根据ID 和数量进行更新的方法 //2.在changgou-service-goods微服务中实现接口 //3.在changgou-service-order微服务中添加依赖 启用feignclients //4.注入使用 //思考下批量进行添加数据 todo //加积分 update tb_user set points =points+#{points} where username = #{username} //调用积分微服务 获取到该活动或者该用户的应该给的积分的值 userFeign.addPoints(order.getUsername(),10); order.setTotalNum(totalNum); order.setTotalMoney(totalMoney); order.setPayMoney(totalMoney); //1.3 设置时间 order.setCreateTime(new Date()); order.setUpdateTime(order.getCreateTime()); //1.4 设置订单所属的用户名 在controller已经设置 这里不用 order.setBuyerRate(“0”);//未评价 //1.5 设置状态 order.setPayStatus(“0”); order.setConsignStatus(“0”); order.setOrderStatus(“0”); order.setIsDelete(“0”); //2.添加数据到订单选项表中 orderMapper.insertSelective(order); // 3 删除购物车的数据 redisTemplate.delete(“Cart“+order.getUsername()); }

  1. 测试数据

{ “receiverContact”:”张云”, “receiverMobile”:”13888888888”, “receiverAddress”:”马路上”, “payType”:”1”, “buyerMessage”:”快点”, “sourceType”:”1” }

  1. <a name="34692c97"></a>
  2. ## 4 支付流程分析
  3. <a name="e0333aef"></a>
  4. ### 4.1 订单支付分析
  5. ![](https://cdn.nlark.com/yuque/0/2021/png/12589476/1636275654948-62382833-d730-468f-af43-b3cdd3174a88.png#)<br />如上图,步骤分析如下:
  6. ```properties
  7. 1.用户下单之后,订单数据会存入到MySQL中,同时会将订单对应的支付日志存入到Redis,以队列的方式存储。
  8. 2.用户下单后,进入支付页面,支付页面调用支付系统,从微信支付获取二维码数据,并在页面生成支付二维码。
  9. 3.用户扫码支付后,微信支付服务器会通调用前预留的回调地址,并携带支付状态信息。
  10. 4.支付系统接到支付状态信息后,将支付状态信息发送给RabbitMQ
  11. 5.订单系统监听RabbitMQ中的消息获取支付状态,并根据支付状态修改订单状态
  12. 6.为了防止网络问题导致notifyurl没有接到对应数据,定时任务定时获取Redis中队列数据去微信支付接口查询状态,并定时更新对应状态。

4.2 二维码创建(了解)

今天主要讲微信支付,后面为了看到效果,我们简单说下利用qrious制作二维码插件。
qrious是一款基于HTML5 Canvas的纯JS二维码生成插件。通过qrious.js可以快速生成各种二维码,你可以控制二维码的尺寸颜色,还可以将生成的二维码进行Base64编码。
qrious.js二维码插件的可用配置参数如下:

参数 类型 默认值 描述
background String “white” 二维码的背景颜色。
foreground String “black” 二维码的前景颜色。
level String “L” 二维码的误差校正级别(L, M, Q, H)。
mime String “image/png” 二维码输出为图片时的MIME类型。
size Number 100 二维码的尺寸,单位像素。
value String “” 需要编码为二维码的值

下面的代码即可生成一张二维码

  1. <html>
  2. <head>
  3. <title>二维码入门小demo</title>
  4. </head>
  5. <body>
  6. <img id="qrious">
  7. <script src="qrious.js"></script>
  8. <script>
  9. var qr = new QRious({
  10. element:document.getElementById('qrious'),
  11. size:250,
  12. level:'H',
  13. value:'http://www.itheima.com'
  14. });
  15. </script>
  16. </body>
  17. </html>

运行效果:
第11章 订单 - 图31
大家掏出手机,扫一下看看是否会看到黑马的官网呢?

5 微信扫码支付简介

5.1微信扫码支付申请

微信扫码支付是商户系统按微信支付协议生成支付二维码,用户再用微信“扫一扫”完成支付的模式。该模式适用于PC网站支付、实体店单品或订单支付、媒体广告支付等场景。
申请步骤:(了解)
第一步:注册公众号(类型须为:服务号)
请根据营业执照类型选择以下主体注册:个体工商户| 企业/公司| 政府| 媒体| 其他类型
第二步:认证公众号
公众号认证后才可申请微信支付,认证费:300元/次。
第三步:提交资料申请微信支付
登录公众平台,点击左侧菜单【微信支付】,开始填写资料等待审核,审核时间为1-5个工作日内。
第四步:开户成功,登录商户平台进行验证
资料审核通过后,请登录联系人邮箱查收商户号和密码,并登录商户平台填写财付通备付金打的小额资金数额,完成账户验证。
第五步:在线签署协议
本协议为线上电子协议,签署后方可进行交易及资金结算,签署完立即生效。
本课程已经提供好“传智播客”的微信支付账号,学员无需申请。

5.2 开发文档

微信支付接口调用的整体思路:
按API要求组装参数,以XML方式发送(POST)给微信支付接口(URL),微信支付接口也是以XML方式给予响应。程序根据返回的结果(其中包括支付URL)生成二维码或判断订单状态。
在线微信支付开发文档:
https://pay.weixin.qq.com/wiki/doc/api/index.html
如果你不能联网,请查阅讲义配套资源 (资源\配套软件\微信扫码支付\开发文档)
我们在本章课程中会用到”统一下单”和”查询订单”两组API

  1. 1. appid:微信公众账号或开放平台APP的唯一标识
  2. 2. mch_id:商户号 (配置文件中的partner)
  3. 3. partnerkey:商户密钥
  4. 4. sign:数字签名, 根据微信官方提供的密钥和一套算法生成的一个加密信息, 就是为了保证交易的安全性

5.3 微信支付模式介绍

5.3.1 模式一

第11章 订单 - 图32
业务流程说明:

  1. 1.商户后台系统根据微信支付规定格式生成二维码(规则见下文),展示给用户扫码。
  2. 2.用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。
  3. 3.微信支付系统收到客户端请求,发起对商户后台系统支付回调URL的调用。调用请求将带productid和用户的openid等参数,并要求商户系统返回交数据包,详细请见"本节3.1回调数据输入参数"
  4. 4.商户后台系统收到微信支付系统的回调请求,根据productid生成商户系统的订单。
  5. 5.商户系统调用微信支付【统一下单API】请求下单,获取交易会话标识(prepay_id
  6. 6.微信支付系统根据商户系统的请求生成预支付交易,并返回交易会话标识(prepay_id)。
  7. 7.商户后台系统得到交易会话标识prepay_id2小时内有效)。
  8. 8.商户后台系统将prepay_id返回给微信支付系统。返回数据见"本节3.2回调数据输出参数"
  9. 9.微信支付系统根据交易会话标识,发起用户端授权支付流程。
  10. 10.用户在微信客户端输入密码,确认支付后,微信客户端提交支付授权。
  11. 11.微信支付系统验证后扣款,完成支付交易。
  12. 12.微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。
  13. 13.微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。
  14. 14.未收到支付通知的情况,商户后台系统调用【查询订单API】。
  15. 15.商户确认订单已支付后给用户发货。

5.3.2 模式二

第11章 订单 - 图33
业务流程说明:

  1. 1.商户后台系统根据用户选购的商品生成订单。
  2. 2.用户确认支付后调用微信支付【统一下单API】生成预支付交易;
  3. 3.微信支付系统收到请求后生成预支付交易单,并返回交易会话的二维码链接code_url
  4. 4.商户后台系统根据返回的code_url生成二维码。
  5. 5.用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。
  6. 6.微信支付系统收到客户端请求,验证链接有效性后发起用户支付,要求用户授权。
  7. 7.用户在微信客户端输入密码,确认支付后,微信客户端提交授权。
  8. 8.微信支付系统根据用户授权完成支付交易。
  9. 9.微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。
  10. 10.微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。
  11. 11.未收到支付通知的情况,商户后台系统调用【查询订单API】。
  12. 12.商户确认订单已支付后给用户发货。