【雷丰阳-谷粒商城 】【分布式高级篇-微服务架构篇】【21】【购物车】

news2024/11/22 22:10:34

持续学习&持续更新中…

守破离


【雷丰阳-谷粒商城 】【分布式高级篇-微服务架构篇】【21】【购物车】

  • 购物车需求描述
  • 购物车数据结构
  • 数据Model抽取
  • 实现流程(参照京东)
  • 代码实现
  • 参考

购物车需求描述

  • 用户可以在登录状态下将商品添加到购物车【用户购物车/在线购物车】

    • 放入数据库
    • mongodb
    • 放入 redis(采用)
    • 登录以后,会将临时购物车的数据全部合并过来,并清空临时购物车
  • 用户可以在未登录状态下将商品添加到购物车【游客购物车/离线购物车/临时购物车】

    • 放入 localstorage
    • cookie
    • WebSQL
    • (客户端存储,后台不存,后台就没法分析用户的购物偏好)
    • 放入 redis(采用,有价值的数据要放在后端存储,便于大数据分析)
    • 浏览器即使关闭,下次进入,临时购物车数据都在
  • 用户可以使用购物车一起结算下单

  • 给购物车添加商品

  • 用户可以查询自己的购物车

  • 用户可以在购物车中修改购买商品的数量。

  • 用户可以在购物车中删除商品。

  • 保存选中不选中商品的状态

  • 在购物车中展示商品优惠信息

  • 提示购物车商品价格变化

注意:真实开发中,最好有一个Redis(集群) 专门负责购物车,不应该跟负责缓存的Redis混合起来使用

购物车数据结构

在这里插入图片描述

每一个购物项信息,都是一个对象,基本字段包括:

{
	skuId: 2131241, 
	check: true, 
	title: "Apple iphone.....", 
	defaultImage: "...", 
	price: 4999, 
	count: 1, 
	totalPrice: 4999, 
	skuSaleVO: {...} 
}

购物车中不止一条数据,因此最终会是对象的数组:

[
	{},
	{},
	...
]

Redis 有 5 种不同数据结构,这里选择哪一种比较合适呢?

  • 首先不同用户应该有独立的购物车,因此购物车应该以用户作为 key 来存储,Value 是 用户的购物车(所有购物项)信息。这样看来基本的k-v结构就可以了。

  • 但是,我们对购物车中的商品进行增、删、改操作,基本都需要根据商品 id 进行判断, 为了方便后期处理,购物车里面也应该是k-v结构,key 是商品 id,value 是这个商品的购物项信息。

  • 综上所述,我们的购物车结构是一个双层Map:Map<String, Map<String, CartItemInfo>> 在这里插入图片描述

  • 第一层 Map,Key 是用户 id ,Value 是用户对应的购物车

  • 第二层 Map,Key 是购物车中的商品 id,Value 是对应商品的购物项信息

在这里插入图片描述

Map<String k1, Map<String k2, CartItemInfo item> userCart>

  • k1:标识每一个用户的购物车
  • k2:购物项的商品id

在Redis中

  • key:用户标识
  • value:Hash(k:商品id,v:购物项详情)

数据Model抽取

/**
 * 整个购物车
 * 需要计算的属性,必须重写他的get方法,保证每次获取属性都会进行计算
 */
public class Cart {

    List<CartItem> items;

    private Integer countNum;//商品数量

    private Integer countType;//商品类型数量

    private BigDecimal totalAmount;//商品总价

    private BigDecimal reduce = new BigDecimal("0.00");//减免价格

    public List<CartItem> getItems() {
        return items;
    }

    public void setItems(List<CartItem> items) {
        this.items = items;
    }

    public Integer getCountNum() {
        int count = 0;
        if (items != null && items.size() > 0) {
            for (CartItem item : items) {
                count += item.getCount();
            }
        }
        return count;
    }


    public Integer getCountType() {
        int count = 0;
        if (items != null && items.size() > 0) {
            for (CartItem item : items) {
                count += 1;
            }
        }
        return count;
    }


    public BigDecimal getTotalAmount() {
        BigDecimal amount = new BigDecimal("0");
        //1、计算购物项总价
        if (items != null && items.size() > 0) {
            for (CartItem item : items) {
                if(item.getCheck()){
                    BigDecimal totalPrice = item.getTotalPrice();
                    amount = amount.add(totalPrice);
                }
            }
        }

        //2、减去优惠总价
        BigDecimal subtract = amount.subtract(getReduce());

        return subtract;
    }


    public BigDecimal getReduce() {
        return reduce;
    }

    public void setReduce(BigDecimal reduce) {
        this.reduce = reduce;
    }
}
/**
 * 购物项内容
 */
public class CartItem {
    private Long skuId;
    private Boolean check = true;
    private String title;
    private String image;
    private List<String> skuAttr;
    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> getSkuAttr() {
        return skuAttr;
    }

    public void setSkuAttr(List<String> skuAttr) {
        this.skuAttr = skuAttr;
    }

    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本来就是<key,value>结构

所以,我们只需要BoundHashOperations<String, Object, Object> hashOperations = stringRedisTemplate.boundHashOps(CartConstant.CART_REDIS_KEY_PREFIX + key);,即可从Redis中获取一个类似于HashMap的对象,充当用户的购物车

保存购物项就可以这样写:hashOperations .put(skuId.toString(), JSON.toJSONString(cartItem));

获取购物项可以这样写:CartItem cartItem = JSON.parseObject(hashOperations.get(skuId.toString()), CartItem.class);

实现流程(参照京东)

在这里插入图片描述

user-key 是随机生成的 id,不管有没有登录都会有这个 cookie 信息。

  • 浏览器有一个cookie;user-key;标识用户身份,一个月后过期;
  • 如果第一次使用jd的购物车功能,都会给一个临时的用户身份;user-key 这个 Cookie
  • 浏览器以后保存,每次访问都会带上这个cookie;
  • 登录:session中有用户信息
  • 没登录:按照cookie里面带来的user-key来做
  • 第一次使用购物车页面:如果没有临时用户user-key,就帮忙创建一个临时用户user-key。

ThreadLocal—同一个线程共享数据:(Map<Thread,Object> threadLocal )

在这里插入图片描述

@ToString
@Data
public class UserInfoTo {
    private Long userId;
    private String userKey; //一定会封装,user-key 是随机生成的 id,不管有没有登录都会有这个 cookie 信息。

    private boolean flag = false; // 只需要让浏览器保存一次user-key这个cookie即可
}
public class CartConstant {
    public static final String TEMP_USER_COOKIE_NAME = "user-key";
    public static final int TEMP_USER_COOKIE_TIMEOUT = 60*60*24*30; // Cookie的有效期 一个月后过期
}
/**
 * 判断用户的登录状态。并封装传递(用户信息)给 controller。命令浏览器保存user-key这个Cookie
 */
public class GulimallCartInterceptor implements HandlerInterceptor {

    //   ThreadLocal: 同一个线程共享数据,可以让Controller等,快速得到用户信息UserInfoTo
    public static final ThreadLocal<UserInfoTo> THREAD_LOCAL = new ThreadLocal<>();

    /**
     * 目标方法执行之前
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        UserInfoTo userInfoTo = new UserInfoTo();

//        request是SpringSession已经包装过的
        MemberRespVo member = (MemberRespVo) request.getSession().getAttribute(AuthServerConstant.LOGIN_USER);
        if (null != member) { // 登录了
            userInfoTo.setUserId(member.getId());
//            利用GULISESSION这个Cookie(SpringSession配置的),也就是sessionId作为user-key合适吗?
//            不合适
//            虽然能判断并获取这个信息,但是你使用了认证服务的信息,符合微服务分模块开发吗?
//            而且这样用的话,会增加复杂性
//            比如在用户没有登陆的情况下,userInfoTo的user-key一定会被设置过了,并且浏览器也保存了这个user-key
//            然后用户再次登录,不能将这个信息作为user-key直接使用了,又得给UserInfoTo再增添一个字段,比如叫userSessionId,反正很麻烦
//            Cookie[] cookies = request.getCookies();
//            if (cookies != null && cookies.length > 0) {
//                for (Cookie cookie : cookies) {
//                    if (cookie.getName().equals("GULISESSION")) {
//                        System.out.println(cookie.getName() + "====>" + cookie.getValue());
//                        break;
//                    }
//                }
//            }
        }

//        判断浏览器有没有带来user-key这个Cookie
        Cookie[] cookies = request.getCookies();
        if (cookies != null && cookies.length > 0) {
            for (Cookie cookie : cookies) {
//                  浏览器有带来user-key这个cookie(不是第一次使用购物车页面)
                if (cookie.getName().equals(CartConstant.TEMP_USER_COOKIE_NAME)) {
                    userInfoTo.setUserKey(cookie.getValue());
                    // 浏览器已经保存了user-key这个Cookie,那么就不需要浏览器再次保存了,如果不设置,那么这个Cookie会无限续期
                    userInfoTo.setFlag(true);
                    break;
                }
            }
        }

//        只要没有user-key,不管你有没有登录,就代表是第一次使用购物车页面,都给你生成一个user-key
        if (StringUtils.isEmpty(userInfoTo.getUserKey())) { // 是第一次使用购物车页面
            String userKey = UUID.randomUUID().toString();
            userInfoTo.setUserKey(userKey);
        }

        THREAD_LOCAL.set(userInfoTo);

        return true;
    }

    /**
     * 业务执行之后;分配临时用户,让浏览器保存user-key
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        UserInfoTo userInfoTo = THREAD_LOCAL.get();

        //如果没有临时用户一定要让浏览器保存一个临时用户
        if (!userInfoTo.isFlag()) {
            Cookie cookie = new Cookie(CartConstant.TEMP_USER_COOKIE_NAME, userInfoTo.getUserKey());
            cookie.setMaxAge(CartConstant.TEMP_USER_COOKIE_TIMEOUT);
            cookie.setDomain("gulimall.com");
            response.addCookie(cookie); // 让浏览器保存user-key这个Cookie
        }
    }

}
@Configuration
public class GulimallCartWebConfig implements WebMvcConfigurer {
    /**
     * 添加拦截器
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new GulimallCartInterceptor()).addPathPatterns("/**");
    }
}

代码实现

两个功能比较重要:新增商品到购物车、查询购物车。

新增商品:判断是否登录

  • 是:则添加商品到后台 Redis 中,把 user 的唯一标识符作为 key。
  • 否:则添加商品到后台 redis 中,使用随机生成的 user-key 作为 key。
  • 购物车里有该商品,更改数量即可
  • 购物车里没有该商品, 添加新商品到购物车

查询购物车列表:判断是否登录

  • 否:直接根据 user-key 查询 redis 中数据并展示
  • 是:已登录,则需要先根据 user-key 查询 redis 是否有数据。
    • 有:合并离线购物车数据到登录用户的购物车,而后查询 redis。
    • 否:直接去后台查询 redis,而后返回。
    private BoundHashOperations<String, Object, Object> getCurrentUserCartHashOps() {
        UserInfoTo userInfoTo = GulimallCartInterceptor.THREAD_LOCAL.get();
        String userKey = userInfoTo.getUserKey();
        Long userId = userInfoTo.getUserId();
        BoundHashOperations<String, Object, Object> hashOperations;
        if (userId != null) { // 登录了,操作登录购物车
            hashOperations = stringRedisTemplate.boundHashOps(CartConstant.CART_REDIS_KEY_PREFIX + userId);
        } else { // 没登陆,操作离线购物车
            hashOperations = stringRedisTemplate.boundHashOps(CartConstant.CART_REDIS_KEY_PREFIX + userKey);
        }
        return hashOperations;
    }

    private List<CartItem> listCartItems(Object userInfoKey) {
        BoundHashOperations<String, Object, Object> ops = stringRedisTemplate.boundHashOps(CartConstant.CART_REDIS_KEY_PREFIX + userInfoKey);
        List<Object> values = ops.values();
        if (null != values && values.size() > 0)
            return values.stream().map(item -> JSON.parseObject(item.toString(), CartItem.class)).collect(Collectors.toList());
        return null;
    }

添加商品到购物车:

    @Override
    public CartItem addToCart(Long skuId, Integer num) throws ExecutionException, InterruptedException {
        BoundHashOperations<String, Object, Object> cartOps = getCurrentUserCartHashOps();
        Object o = cartOps.get(skuId.toString());
        if (o != null) {
            // 购物车里有该商品,更改数量即可
            String cartItemJSON = o.toString();
            CartItem cartItem = JSON.parseObject(cartItemJSON, CartItem.class);
            cartItem.setCount(cartItem.getCount() + num);
            cartOps.put(skuId.toString(), JSON.toJSONString(cartItem));
            return cartItem;
        } else {
            // 购物车里没有该商品 添加新商品到购物车

            CartItem cartItem = new CartItem();

            //1、远程查询当前要添加的商品的信息
            CompletableFuture<Void> getSkuInfoTask = CompletableFuture.runAsync(() -> {
                R skuInfo = productFeignService.getSkuInfo(skuId);
                SkuInfoVo data = skuInfo.getData("skuInfo", new TypeReference<SkuInfoVo>() {
                });
                cartItem.setCheck(true);
                cartItem.setCount(num);
                cartItem.setImage(data.getSkuDefaultImg());
                cartItem.setTitle(data.getSkuTitle());
                cartItem.setSkuId(skuId);
                cartItem.setPrice(data.getPrice());
            }, executor);

            //2、远程查询sku的组合信息
            CompletableFuture<Void> getSkuSaleAttrValues = CompletableFuture.runAsync(() -> {
                List<String> values = productFeignService.getSkuSaleAttrValues(skuId);
                cartItem.setSkuAttr(values);
            }, executor);

            CompletableFuture.allOf(getSkuInfoTask, getSkuSaleAttrValues).get();
            String s = JSON.toJSONString(cartItem);
            cartOps.put(skuId.toString(), s);

            return cartItem;
        }
    }

获取用户的购物车:

    @Override
    public Cart getCart() throws ExecutionException, InterruptedException {
        UserInfoTo userInfoTo = GulimallCartInterceptor.THREAD_LOCAL.get();
        Long userId = userInfoTo.getUserId();
        String userKey = userInfoTo.getUserKey();

        Cart cart = new Cart();

        if (userId != null) {
            // 如果用户有离线购物车,需要合并离线购物车到登录购物车,并且清空临时购物车
            List<CartItem> tempCartItems = listCartItems(userKey);
            if (null != tempCartItems && tempCartItems.size() > 0) {
                for (CartItem tempCartItem : tempCartItems) {
                    addToCart(tempCartItem.getSkuId(), tempCartItem.getCount()); // 合并离线购物车的购物项到登录购物车
                }
                CompletableFuture.runAsync(() -> {
                    stringRedisTemplate.delete(CartConstant.CART_REDIS_KEY_PREFIX + userKey); // 清空临时购物车
                }, executor);
            }
            // 登录了,展示登录购物车
            List<CartItem> loginCartItems = listCartItems(userId);
            cart.setItems(loginCartItems);
        } else {
            // 没登陆,展示离线购物车
            List<CartItem> tempCartItems = listCartItems(userKey);
            cart.setItems(tempCartItems);
        }

        return cart;
    }

controller使用:

   /**
     * 浏览器有一个cookie;user-key;标识用户身份,一个月后过期;
     * 如果第一次使用jd的购物车功能,都会给一个临时的用户身份;user-key这个Cookie
     * 浏览器以后保存,每次访问都会带上这个cookie;
     * <p>
     * 登录:session有用户信息
     * 没登录:按照cookie里面带来user-key来做
     * 第一次使用购物车页面:如果没有临时用户user-key,帮忙创建一个临时用户user-key。
     */
    @GetMapping("/cart.html")
    public String cartListPage(Model model) throws ExecutionException, InterruptedException {
//        Cart cart = cartService.getCart();
//        model.addAttribute("cart",cart);
//        UserInfoTo userInfoTo = GulimallCartInterceptor.THREAD_LOCAL.get();
//        System.out.println("CartController ===> " + userInfoTo);

        Cart cart = cartService.getCart();
        model.addAttribute("cart", cart);
        return "cartList";
    }

    /**
     * 添加商品到购物车
     * http://cart.gulimall.com/addToCart?skuId=1&num=1
     * 
     * RedirectAttributes ra
     *      ra.addFlashAttribute();将数据放在session里面可以在页面取出,但是只能取一次
     *      ra.addAttribute("skuId",skuId);将数据放在url后面
     */
    @GetMapping("/addToCart")
    public String addToCart(@RequestParam("skuId") Long skuId, @RequestParam("num") Integer num, RedirectAttributes redirectAttributes) throws ExecutionException, InterruptedException {
        CartItem cartItem = cartService.addToCart(skuId, num);
        redirectAttributes.addAttribute("skuId", skuId);
        return "redirect:http://cart.gulimall.com/addToCartSuccess"; //重定向到成功页面,防止用户刷新页面再次提交数据添加到购物车
    }

    /**
     * 跳转到成功页
     */
    @GetMapping("/addToCartSuccess")
    public String addToCartSuccess(@RequestParam("skuId") Long skuId, Model model) {
        //添加商品到购物车成功,再次查询购物车数据即可
        CartItem item = cartService.getCartItem(skuId);
        model.addAttribute("item", item);
        return "success";
    }

参考

雷丰阳: Java项目《谷粒商城》Java架构师 | 微服务 | 大型电商项目.


本文完,感谢您的关注支持!


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1904846.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从FasterTransformer源码解读开始了解大模型(2.1)代码通读03

从FasterTransformer源码解读开始了解大模型&#xff08;2.2&#xff09;代码解读03-forward函数 写在前面的话 本篇的内容继续解读forward函数&#xff0c;从650行开始进行解读 零、输出Context_embeddings和context_cum_log_probs的参数和逻辑 从653行开始&#xff0c;会…

Python实现ABC人工蜂群优化算法优化随机森林回归模型(RandomForestRegressor算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 人工蜂群算法(Artificial Bee Colony, ABC)是由Karaboga于2005年提出的一种新颖的基于群智能的全局优化…

LeetCode Hard|124.二叉树中的最大路径和

力扣题目链接 题目解读&#xff1a; 二叉树路径的定义即从1.任意节点出发&#xff0c;到达任意节点&#xff1b;2.该路径至少包含一个节点&#xff0c;且不一定经过跟节点&#xff1b;3.求所有可能路径和的最大值。 也就是说路径途径一个节点只能选择来去两个方向 考虑一个二叉…

微信公众平台测试账号本地微信功能测试说明

使用场景 在本地测试微信登录功能时&#xff0c;因为微信需要可以互联网访问的域名接口&#xff0c;所以本地使用花生壳做内网穿透&#xff0c;将前端服务的端口和后端服务端口进行绑定&#xff0c;获得花生壳提供的两个外网域名。 微信测试账号入口 绑定回调接口 回调接口的…

2024年06月CCF-GESP编程能力等级认证Python编程二级真题解析

本文收录于专栏《Python等级认证CCF-GESP真题解析》&#xff0c;专栏总目录&#xff1a;点这里&#xff0c;订阅后可阅读专栏内所有文章。 一、单选题&#xff08;每题 2 分&#xff0c;共 30 分&#xff09; 第 1 题 小杨父母带他到某培训机构给他报名参加CCF组织的GESP认证…

声明队列和交换机 + 消息转换器

目录 1、声明队列和交换机 方法一&#xff1a;基于Bean的方式声明 方法二&#xff1a;基于Spring注解的方式声明 2、消息转换器 1、声明队列和交换机 方法一&#xff1a;基于Bean的方式声明 注&#xff1a;队列和交换机的声明是放在消费者这边的&#xff0c;这位发送的人他…

力扣206

题目 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1]示例 2&#xff1a; 输入&#xff1a;head [1,2] 输出&#xff1a;[2,1]示例 3&#xff1a; 输…

【排序算法】—— 快速排序

快速排序的原理是交换排序&#xff0c;其中qsort函数用的排序原理就是快速排序&#xff0c;它是一种效率较高的不稳定函数&#xff0c;时间复杂度为O(N*longN)&#xff0c;接下来就来学习一下快速排序。 一、快速排序思路 1.整体思路 以升序排序为例&#xff1a; (1)、首先随…

PTA甲级1005:Spell It Right

错误代码&#xff1a; #include<iostream> #include<vector> #include<unordered_map> using namespace std;int main() {unordered_map<int, string> map {{0, "zero"}, {1, "one"}, {2, "two"}, {3, "three&qu…

有一个日期(Date)类的对象和一个时间(Time)类的对象,均已指定了内容,要求一次输出其中的日期和时间

可以使用友元成员函数。在本例中除了介绍有关友元成员函数的简单应用外&#xff0c;还将用到类的提前引用声明&#xff0c;请读者注意。编写程序&#xff1a; 运行结果&#xff1a; 程序分析&#xff1a; 在一般情况下&#xff0c;两个不同的类是互不相干的。display函…

实验六 图像的傅立叶变换

一&#xff0e;实验目的 1了解图像变换的意义和手段&#xff1b; 2熟悉傅立叶变换的基本性质&#xff1b; 3熟练掌握FFT变换方法及应用&#xff1b; 4通过实验了解二维频谱的分布特点&#xff1b; 5通过本实验掌握利用MATLAB编程实现数字图像的傅立叶变换。 6评价人眼对图…

股票Level-2行情是什么,应该怎么使用,从哪里获取数据

行情接入方法 level2行情websocket接入方法-CSDN博客 相比传统的股票行情&#xff0c;Level-2行情为投资者打开了更广阔的视野&#xff0c;不仅限于买一卖一的表面数据&#xff0c;而是深入到市场的核心&#xff0c;提供了十档乃至千档的行情信息&#xff08;沪市十档&#…

关于MCU-Cortex M7的存储结构(flash与SRAM)

MCU并没有DDR&#xff0c;所以他把代码存储在flash上&#xff0c;临时变量和栈运行在SRAM上。之所以这么做是因为MCU的CPU频率很低&#xff0c;一般低于500MHZ&#xff0c;flash的读取速度能够满足CPU的取指需求&#xff0c;但flash 的写入速度很慢&#xff0c;所以引入了SRAM …

【数据结构与算法】快速排序挖坑法

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《数据结构与算法》 期待您的关注 ​

go语言day11 错误 defer(),panic(),recover()

错误&#xff1a; 创建错误 1&#xff09;fmt包下提供的方法 fmt.Errorf(" 格式化字符串信息 " &#xff0c; 空接口类型对象 ) 2&#xff09;errors包下提供的方法 errors.New(" 字符串信息 ") 创建自定义错误 需要实现error接口&#xff0c;而error接口…

【植物大战僵尸杂交版】获取+存档插件

文章目录 一、还记得《植物大战僵尸》吗&#xff1f;二、在哪下载&#xff0c;怎么安装&#xff1f;三、杂交版如何进行存档功能概述 一、还记得《植物大战僵尸》吗&#xff1f; 最近&#xff0c;一款曾经在15年前风靡一时的经典游戏《植物大战僵尸》似乎迎来了它的"文艺复…

C++初学者指南-5.标准库(第一部分)--迭代器

C初学者指南-5.标准库(第一部分)–迭代器 Iterators 文章目录 C初学者指南-5.标准库(第一部分)--迭代器 Iterators1.默认正向迭代器2.反向迭代器3.基于迭代器的循环4.示例&#xff1a;交换相邻的一对元素5.迭代器范围6.迭代器范围中的元素数量7. 总结&#xff1a;迭代器 指向某…

Vue笔记11-Composition API的优势

Options API存在的问题 使用传统Options API中&#xff0c;新增或者修改一个需求&#xff0c;就需要分别在data&#xff0c;methods&#xff0c;computed里修改&#xff0c;而这些选项分布在代码的各个地方&#xff0c;中间还穿插着其他Optional API&#xff0c;如果代码量上来…

国产化新标杆:TiDB 助力广发银行新一代总账系统投产上线

随着全球金融市场的快速发展和数字化转型的深入推进&#xff0c;金融科技已成为推动银行业创新的核心力量。特别是在当前复杂多变的经济环境下&#xff0c;银行业务的高效运作和风险管理能力显得尤为重要。总账系统作为银行会计信息系统的核心&#xff0c;承载着记录、处理和汇…

运维锅总详解系统启动流程

本文详细介绍Linux及Windows系统启动流程&#xff0c;并分析了它们启动流程的异同以及造成这种异同的原因。希望本文对您理解系统的基本启动流程有所帮助&#xff01; 一、Linux系统启动流程 Linux 系统的启动流程可以分为几个主要阶段&#xff0c;从电源开启到用户登录。每个…