购物车服务-----功能实现逻辑1

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 购物车服务-----功能实现逻辑

整个购物车的数据存储都是放到redis进行存储的,同时购物车分为临时购物车和用户购物车,临时购物车就是用户没有登录时的购物车,用户购物车是用户登录了的购物车,用户登录了后会把临时购物车的购物项合并在用户自己的购物车里面并且清空临时购物车,这里最核心的是理解购物车存入redis的结构


购物车和购物项存入redis的结构用的是HashMap结构


Hash值为cartKey ,表示购物车,其中的 map结构为: Map<String skuId,String cartItem>,表示购物项


cartKey表示格式为saodaimall:cart:key,表示购物车,其中saodaimall:cart:key的saodaimall:cart:是一个固定前缀,key值有两种,如果登录了就是用户id(saodaimall:cart:1),没登录就是名为user-key的cookie的值(例如saodaimall:cart:a191459a-0f24-4e69-8dc7-a3f81de96202)


Map<String skuId,String cartItem>作为外表示用户购物车的里的购物项,购物项中skuId为商品id作为key,values作为购物项的详细信息

购物车实现流程(加入购物车->去购物车结算)

1、抽取购物车和购物项模型

package com.saodai.saodaimall.cart.vo;
import org.springframework.util.CollectionUtils;
import java.math.BigDecimal;
import java.util.List;
/**
*  购物车VO模型
* 需要计算的属性需要重写get方法,保证每次获取属性都会进行计算(这里没用@Data是因为要重写get方法)
**/
public class CartVo {
    /**
    * 购物项信息
    */
    List<CartItemVo> items;
    /**
    * 商品数量
    */
    private Integer countNum;
    /**
    * 商品类型数量
    */
    private Integer countType;
    /**
    * 商品总价
    */
    private BigDecimal totalAmount;
    /**
    * 减免价格
    */
    private BigDecimal reduce = new BigDecimal("0.00");;
    public List<CartItemVo> getItems() {
        return items;
    }
    public void setItems(List<CartItemVo> items) {
        this.items = items;
    }
    //获取购物车中所有商品数量总和
    public Integer getCountNum() {
        int count = 0;
        if (items != null && items.size() > 0) {
            for (CartItemVo item : items) {
                count += item.getCount();
            }
        }
        return count;
    }
    //获取购物车中所有商品类型总和
    public Integer getCountType() {
        int count = 0;
        if (items != null && items.size() > 0) {
            for (CartItemVo item : items) {
                count += 1;
            }
        }
        return count;
    }
    //获取购物车中所有商品价格总和
    public BigDecimal getTotalAmount() {
        BigDecimal amount = new BigDecimal("0");
        // 计算购物项总价
        if (!CollectionUtils.isEmpty(items)) {
            for (CartItemVo cartItem : items) {
                if (cartItem.getCheck()) {
                    amount = amount.add(cartItem.getTotalPrice());
                }
            }
        }
        // 计算优惠后的价格
        return amount.subtract(getReduce());
    }
    public BigDecimal getReduce() {
        return reduce;
    }
    public void setReduce(BigDecimal reduce) {
        this.reduce = reduce;
    }
}
package com.saodai.saodaimall.cart.vo;
import java.math.BigDecimal;
import java.util.List;
/**
 * 购物项Vo模型(这里没用@Data是因为要重写get方法)
 **/
public class CartItemVo {
    //购物项中商品skuId
    private Long skuId;
    //是否被选中
    private Boolean check = true;
    //购物项中商品标题
    private String title;
    //购物项中商品图片
    private String image;
    /**
     * 商品属性套餐(就是各种销售属性的组合)
     */
    private List<String> skuAttrValues;
    //购物项中商品单个价格
    private BigDecimal price;
    //购物项中商品数量
    private Integer count;
    //购物项中商品总价
    private BigDecimal totalPrice;
    public Long getSkuId() {
        return skuId;
    }
    public void setSkuId(Long skuId) {
        this.skuId = skuId;
    }
    public Boolean getCheck() {
        return check;
    }
    public void setCheck(Boolean check) {
        this.check = check;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getImage() {
        return image;
    }
    public void setImage(String image) {
        this.image = image;
    }
    public List<String> getSkuAttrValues() {
        return skuAttrValues;
    }
    public void setSkuAttrValues(List<String> skuAttrValues) {
        this.skuAttrValues = skuAttrValues;
    }
    public BigDecimal getPrice() {
        return price;
    }
    public void setPrice(BigDecimal price) {
        this.price = price;
    }
    public Integer getCount() {
        return count;
    }
    public void setCount(Integer count) {
        this.count = count;
    }
    /**
     * 计算当前购物项总价
     * @return
     */
    public BigDecimal getTotalPrice() {
        return this.price.multiply(new BigDecimal("" + this.count));
    }
    public void setTotalPrice(BigDecimal totalPrice) {
        this.totalPrice = totalPrice;
    }
}


2、设置登录拦截器

在执行目标方法之前,判断用户的登录状态.并封装传递给controller目标请求:(封装UserInfoTo对象)

(1)获得当前登录用户的信息,用户登录了就设置用户id为userInfoTo的id(通过springsession实现了各个服务直接session共享)


(2)判断用户是不是第一次使用本网站(如果之前用过就会有一个浏览器名为user-key的cookie的值),用过就把cookie的值封装到UserInfoTo对象


(3)没有用过这个网站就需要创建一个新的cookie值(临时用户)


(4)把封装好的userInfoTo对象放到ThreadLocal中


注意:用户登录和有没有用过这个网站是两回事,因为可能是第一次登录,那就可以用过也可能没有用过这个网站


补充:


ThreadLocal 叫做本地线程变量,ThreadLocal是解决线程安全问题,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。为什么能够解决变量并发访问的冲突问题呢?因为一个ThreadLocal的变量只有当前自身线程可以访问,别的线程都访问不了


在Java的多线程编程中,为保证多个线程对共享变量的安全访问,通常会使用synchronized来保证同一时刻只有一个线程对共享变量进行操作。这种情况下可以将类变量放到ThreadLocal类型的对象中,使变量在每个线程中都有独立拷贝,不会出现一个线程读取变量时而被另一个线程修改的现象。最常见的ThreadLocal使用场景为用来解决数据库连接、Session管理等


理解:这里把用户登录信息(对象)放到ThreadLocal中也是围栏解决线程安全问题,这样每个服务都会有登录信息对象的拷贝,那么用户在用户服务中修改的这个登录信息不会影响到其他服务的登录信息,其他服务都是不同线程的登录信息的一个拷贝


业务执行之后,分配临时用户来浏览器保存

(1)从ThreadLocal中获取当前用户的值(已经经过拦截器了)

(2)如果没有用过这个网站就新创建一个临时用户,把前面创建的cookie值赋值到这个cookie里

package com.saodai.saodaimall.cart.interceptor;
import com.saodai.common.vo.MemberResponseVo;
import com.saodai.saodaimall.cart.to.UserInfoTo;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.UUID;
import static com.saodai.common.constant.AuthServerConstant.LOGIN_USER;
import static com.saodai.common.constant.CartConstant.TEMP_USER_COOKIE_NAME;
import static com.saodai.common.constant.CartConstant.TEMP_USER_COOKIE_TIMEOUT;
/**
 *  在执行目标方法之前,判断用户的登录状态.并封装传递给controller目标请求
 **/
public class CartInterceptor implements HandlerInterceptor {
    public static ThreadLocal<UserInfoTo> toThreadLocal = new ThreadLocal<>();
    /***
     * 目标方法执行之前
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
         //拦截器判断用户登录状态的封装类
        UserInfoTo userInfoTo = new UserInfoTo();
        HttpSession session = request.getSession();
        //获得当前登录用户的信息
        MemberResponseVo memberResponseVo = (MemberResponseVo) session.getAttribute(LOGIN_USER);
        if (memberResponseVo != null) {
            //表示用户登录了就设置用户id为userInfoTo的id(用于后面做为redis的部分key值)
            userInfoTo.setUserId(memberResponseVo.getId());
        }
        //判断用户是不是第一次使用本网站(如果之前用过就会有一个浏览器名为user-key的cookie的值)
        Cookie[] cookies = request.getCookies();
        if (cookies != null && cookies.length > 0) {
            for (Cookie cookie : cookies) {
                //user-key
                String name = cookie.getName();
                if (name.equals(TEMP_USER_COOKIE_NAME)) {
                    //浏览器名为user-key的cookie的值
                    userInfoTo.setUserKey(cookie.getValue());
                    //标记为已是临时用户
                    userInfoTo.setTempUser(true);
                }
            }
        }
        //没有用过这个网站就需要创建一个新的cookie值(临时用户)
        if (StringUtils.isEmpty(userInfoTo.getUserKey())) {
            String uuid = UUID.randomUUID().toString();
            userInfoTo.setUserKey(uuid);
        }
        //目标方法执行之前
        toThreadLocal.set(userInfoTo);
        return true;
    }
    /**
     * 业务执行之后,分配临时用户来浏览器保存
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        //获取当前用户的值(已经经过拦截器了)
        UserInfoTo userInfoTo = toThreadLocal.get();
        //如果没有用过这个网站就新创建一个临时用户,把前面创建的cookie值赋值到这个cookie里
        if (!userInfoTo.getTempUser()) {
            //创建一个cookie
            Cookie cookie = new Cookie(TEMP_USER_COOKIE_NAME, userInfoTo.getUserKey());
            //扩大作用域
            cookie.setDomain("saodaimall.com");
            //设置过期时间
            cookie.setMaxAge(TEMP_USER_COOKIE_TIMEOUT);
            response.addCookie(cookie);
        }
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    }
}
package com.saodai.saodaimall.cart.to;
import lombok.Data;
/**
 *拦截器判断用户登录状态的封装类
 **/
@Data
public class UserInfoTo {
    /**
     * 已经登录用户的id
     */
    private Long userId;
    /**
     * 浏览器名为user-key的cookie的值
     */
    private String userKey;
    /**
     * 是否临时用户
     */
    private Boolean tempUser = false;
}
package com.saodai.common.vo;
import lombok.Data;
import lombok.ToString;
import java.io.Serializable;
import java.util.Date;
/**
 *会员信息
 **/
@ToString
@Data
public class MemberResponseVo implements Serializable {
    private static final long serialVersionUID = 5573669251256409786L;
    private Long id;
    /**
     * 会员等级id
     */
    private Long levelId;
    /**
     * 用户名
     */
    private String username;
    /**
     * 密码
     */
    private String password;
    /**
     * 昵称
     */
    private String nickname;
    /**
     * 手机号码
     */
    private String mobile;
    /**
     * 邮箱
     */
    private String email;
    /**
     * 头像
     */
    private String header;
    /**
     * 性别
     */
    private Integer gender;
    /**
     * 生日
     */
    private Date birth;
    /**
     * 所在城市
     */
    private String city;
    /**
     * 职业
     */
    private String job;
    /**
     * 个性签名
     */
    private String sign;
    /**
     * 用户来源
     */
    private Integer sourceType;
    /**
     * 积分
     */
    private Integer integration;
    /**
     * 成长值
     */
    private Integer growth;
    /**
     * 启用状态
     */
    private Integer status;
    /**
     * 注册时间
     */
    private Date createTime;
    /**
     * 社交登录用户的ID
     */
    private String socialId;
    /**
     * 社交登录用户的名称
     */
    private String socialName;
    /**
     * 社交登录用户的自我介绍
     */
    private String socialBio;
}

3、在config包中配置注册刚才的拦截器

package com.saodai.saodaimall.cart.config;
import com.saodai.saodaimall.cart.interceptor.CartInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new CartInterceptor()).addPathPatterns("/**");
    }
}

4、在config包中配置springSession实现登录信息共享

package com.saodai.saodaimall.cart.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;
/**
 * springSession配置类(所有要使用session的服务的session配置要一致)
 */
@Configuration
public class GulimallSessionConfig {
    /**
     * 配置session(主要是为了放大session作用域)
     * @return
     */
    @Bean
    public CookieSerializer cookieSerializer() {
        DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
        //放大作用域
        cookieSerializer.setDomainName("saodaimall.com");
        cookieSerializer.setCookieName("SAODAISESSION");
        return cookieSerializer;
    }
    /**
     * 配置Session放到redis存储的格式为json(其实就是json序列化)
     * @return
     */
    @Bean
    public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
        return new GenericJackson2JsonRedisSerializer();
    }
}


5、加入购物车的购物项

(1)前台加入购物车按钮点击后发送请求/addCartItem给后端

   <div class="box-btns-two"
      th:if="${item.seckillSkuVo == null }">
      <a class="addToCart" href="#" th:attr="skuId=${item.info.skuId}">
      加入购物车
      </a>
   </div>


这里没有直接写成href = "http://cart.saodaimall.com/addCartItem是因为还需要传入skuId和num两个参数给后端,其实也可以直接写,只是不好看,所以在下面的js发请求

   $(".addToCart").click(function () {
        let skuId = $(this).attr("skuId");
        let num = $("#productNum").val();
        location.href = "http://cart.saodaimall.com/addCartItem?skuId=" + skuId + "&num=" + num;
        return false;
    });

(2)购物车服务的CartController来处理/addCartItem请求

/**
* 添加商品到购物车
* attributes.addFlashAttribute():将数据放在session中,可以在页面中取出,但是只能取一次
* attributes.addAttribute():将数据放在url后面
* @return
*/
@GetMapping(value = "/addCartItem")
public String addCartItem(@RequestParam("skuId") Long skuId,
                          @RequestParam("num") Integer num,
                          RedirectAttributes attributes) throws ExecutionException, InterruptedException {
    cartService.addToCart(skuId, num);
    /**
    *  CartItemVo cartItemVo = cartService.addToCart(skuId, num);
    *  model.addAttribute("cartItem",cartItemVo);
    *  没有直接向上面一样这样写是因为要防止每一次刷新页面就再添加商品的情况(类似表单的重复提交)
    */
    //把skuId放到作用域中是为了让下面的addToCartSuccessPage拿到skuId
    attributes.addAttribute("skuId",skuId);
    return "redirect:http://cart.saodaimall.com/addToCartSuccessPage.html";
    }
    /**
     * 跳转到添加购物车成功页面
     * @param skuId
     * @param model
     * @return
     */
    @GetMapping(value = "/addToCartSuccessPage.html")
    public String addToCartSuccessPage(@RequestParam("skuId") Long skuId,
                                       Model model) {
        //重定向到成功页面。再次查询购物车数据即可
        CartItemVo cartItemVo = cartService.getCartItem(skuId);
        model.addAttribute("cartItem",cartItemVo);
        return "success";
    }

分流程:


1、绑定HashMap结构的hash值,便于后面操作HashMap


2、判断Redis是否有该商品的信息


3、如果没有就添加数据,有就只需要修改购物车中的数量就行了


没有:(封装CartItemVo对象)


1>远程查询当前要添加商品的信息


2>远程查询skuAttrValues组合信息


3>把封装好的CartItemVo对象存到Redis缓存中


有:


1>从Redis中拿到当前商品的CartItemVo对象的Json字符串的值,然后转成CartItemVo对象


2>修改CartItemVo对象的购物项数量后又转成Json字符串存入Redis中

/**
     * 添加商品到购物车
          购物车和购物项存入redis的结构用的是HashMap结构
          Hash值为cartKey ,表示购物车,其中的 map结构为: Map<String skuId,String cartItem>,表示购物项
          cartKey表示格式为saodaimall:cart:key,表示购物车,其中saodaimall:cart:key的saodaimall:cart:是一个固定前缀,key值有两种,如果登录了就是用户id(saodaimall:cart:1),没登录就是名为user-key的cookie的值(例如saodaimall:cart:a191459a-0f24-4e69-8dc7-a3f81de96202)
          Map<String skuId,String cartItem>作为外表示用户购物车的里的购物项,购物项中skuId为商品id作为key,values作为购物项的详细信息
     */
    @Override
    public CartItemVo addToCart(Long skuId, Integer num) throws ExecutionException, InterruptedException {
        //拿到要操作的购物车信息
        BoundHashOperations<String, Object, Object> cartOps = getCartOps();
        //判断Redis是否有该商品的信息
        String productRedisValue = (String) cartOps.get(skuId.toString());
        //如果没有就添加数据
        if (StringUtils.isEmpty(productRedisValue)) {
            //2、添加新的商品到购物车(redis)
            //封装购物项
            CartItemVo cartItemVo = new CartItemVo();
            //开启第一个异步任务
            CompletableFuture<Void> getSkuInfoFuture = CompletableFuture.runAsync(() -> {
                //1、远程查询当前要添加商品的信息
                R productSkuInfo = productFeignService.getInfo(skuId);
                SkuInfoVo skuInfo = productSkuInfo.getData("skuInfo", new TypeReference<SkuInfoVo>() {});
                //数据赋值操作
                cartItemVo.setSkuId(skuInfo.getSkuId());
                cartItemVo.setTitle(skuInfo.getSkuTitle());
                cartItemVo.setImage(skuInfo.getSkuDefaultImg());
                cartItemVo.setPrice(skuInfo.getPrice());
                cartItemVo.setCount(num);
            }, executor);
            //开启第二个异步任务
            CompletableFuture<Void> getSkuAttrValuesFuture = CompletableFuture.runAsync(() -> {
                //2、远程查询skuAttrValues组合信息
                List<String> skuSaleAttrValues = productFeignService.getSkuSaleAttrValues(skuId);
                cartItemVo.setSkuAttrValues(skuSaleAttrValues);
            }, executor);
            //等待所有的异步任务全部完成
            CompletableFuture.allOf(getSkuInfoFuture, getSkuAttrValuesFuture).get();
            String cartItemJson = JSON.toJSONString(cartItemVo);
            cartOps.put(skuId.toString(), cartItemJson);
            return cartItemVo;
        } else {
            //购物车有此商品,修改数量即可
            CartItemVo cartItemVo = JSON.parseObject(productRedisValue, CartItemVo.class);
            cartItemVo.setCount(cartItemVo.getCount() + num);
            //修改redis的数据
            String cartItemJson = JSON.toJSONString(cartItemVo);
            cartOps.put(skuId.toString(),cartItemJson);
            return cartItemVo;
        }
    }
  /**
     * 获取到我们要操作的购物车
         购物车和购物项存入redis的结构用的是HashMap结构
          Hash值为cartKey ,表示购物车,其中的 map结构为: Map<String skuId,String cartItem>,表示购物项
          cartKey表示格式为saodaimall:cart:key,表示购物车,其中saodaimall:cart:key的saodaimall:cart:是一个固定前缀,key值有两种,如果登录了就是用户id(saodaimall:cart:1),没登录就是名为user-key的cookie的值(例如saodaimall:cart:a191459a-0f24-4e69-8dc7-a3f81de96202)
          Map<String skuId,String cartItem>作为外表示用户购物车的里的购物项,购物项中skuId为商品id作为key,values作为购物项的详细信息
     */
    private BoundHashOperations<String, Object, Object> getCartOps() {
        //先从拦截器中得到当前用户信息
        UserInfoTo userInfoTo = CartInterceptor.toThreadLocal.get();
        //cartKey是存在redis的key值
        String cartKey = "";
        if (userInfoTo.getUserId() != null) {
            //用户登录了就用用户的id号
            cartKey = CART_PREFIX + userInfoTo.getUserId();
        } else {
            //没登录就用浏览器中名为user-key的cookie的值
            cartKey = CART_PREFIX + userInfoTo.getUserKey();
        }
        //绑定要操作的哈希值,也就是cartKey
        BoundHashOperations<String, Object, Object> operations = redisTemplate.boundHashOps(cartKey);
        return operations;
    }
/**
     * 根据skuId的值查询到redis中的购物项对象
     * @param skuId
     * @return
     */
    @Override
    public CartItemVo getCartItem(Long skuId) {
        //拿到要操作的购物车信息
        BoundHashOperations<String, Object, Object> cartOps = getCartOps();
        String redisValue = (String) cartOps.get(skuId.toString());
        CartItemVo cartItemVo = JSON.parseObject(redisValue, CartItemVo.class);
        return cartItemVo;
    }
package com.saodai.saodaimall.cart.vo;
import java.math.BigDecimal;
import java.util.List;
/**
 * 购物项Vo模型(这里没用@Data是因为要重写get方法)
 **/
public class CartItemVo {
    //购物项中商品skuId
    private Long skuId;
    //是否被选中
    private Boolean check = true;
    //购物项中商品标题
    private String title;
    //购物项中商品图片
    private String image;
    /**
     * 商品属性套餐(就是各种销售属性的组合)
     */
    private List<String> skuAttrValues;
    //购物项中商品单个价格
    private BigDecimal price;
    //购物项中商品数量
    private Integer count;
    //购物项中商品总价
    private BigDecimal totalPrice;
    public Long getSkuId() {
        return skuId;
    }
    public void setSkuId(Long skuId) {
        this.skuId = skuId;
    }
    public Boolean getCheck() {
        return check;
    }
    public void setCheck(Boolean check) {
        this.check = check;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getImage() {
        return image;
    }
    public void setImage(String image) {
        this.image = image;
    }
    public List<String> getSkuAttrValues() {
        return skuAttrValues;
    }
    public void setSkuAttrValues(List<String> skuAttrValues) {
        this.skuAttrValues = skuAttrValues;
    }
    public BigDecimal getPrice() {
        return price;
    }
    public void setPrice(BigDecimal price) {
        this.price = price;
    }
    public Integer getCount() {
        return count;
    }
    public void setCount(Integer count) {
        this.count = count;
    }
    /**
     * 计算当前购物项总价
     * @return
     */
    public BigDecimal getTotalPrice() {
        return this.price.multiply(new BigDecimal("" + this.count));
    }
    public void setTotalPrice(BigDecimal totalPrice) {
        this.totalPrice = totalPrice;
    }
}
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
5月前
|
前端开发 数据库 索引
前后端分离------后端创建笔记(05)用户列表查询接口(下)
前后端分离------后端创建笔记(05)用户列表查询接口(下)
要会创建接口------支付系统19-------支付宝支付-----统一收单下单并支付页面接口----接口说明,接口文档中应该对如何调用接口进行一个详细的说明
要会创建接口------支付系统19-------支付宝支付-----统一收单下单并支付页面接口----接口说明,接口文档中应该对如何调用接口进行一个详细的说明
|
4月前
|
前端开发 API
支付系统27-------梳理一下支付按钮,前端的代码
支付系统27-------梳理一下支付按钮,前端的代码
|
6月前
|
小程序
外卖小程序-购物车模块表结构设计和后端代码
外卖小程序-购物车模块表结构设计和后端代码
58 0
|
6月前
|
JavaScript
基础购物车功能
基础购物车功能
|
6月前
|
缓存 前端开发 JavaScript
若依框架中的权限控制逻辑 ---- 菜单
若依框架中的权限控制逻辑 ---- 菜单
601 0
|
12月前
|
SQL 前端开发 JavaScript
Layui框架实现用户系统-----增删改查
Layui框架实现用户系统-----增删改查
82 0
|
存储 小程序
小程序实现购物车功能
购物车功能是电商小程序中比较常见的功能之一,实现起来也比较简单。通过本文的介绍,我们可以学习到如何将用户所选的商品信息保存在本地,如何展示商品信息,如何计算商品总价和总数,以及如何提供用户对购物车中商品的操作。在实际开发中,还可以根据具体需求进行定制和优化,例如添加优惠券、满减活动等功能,提升用户购物体验。
312 0
|
缓存 NoSQL Redis
购物车服务-----功能实现逻辑2
购物车服务-----功能实现逻辑2
74 0
|
JavaScript Java 数据库
订单服务-----功能实现逻辑2
订单服务-----功能实现逻辑2
201 0