美多商城项目:结算订单与提交订单

article/2025/8/19 18:56:27

一、结算订单

 

1. 结算订单逻辑分析

结算订单是从Redis购物车中查询出被勾选的商品信息进行结算并展示。

2. 结算订单接口设计和定义

1.请求方式

选项方案
请求方法GET
请求地址/orders/settlement/

3. 结算订单后端逻辑实现

class OrderSettlementView(LoginRequiredMixin, View):"""结算订单"""def get(self, request):"""提供订单结算页面"""# 获取登录用户user = request.user# 查询当前用户的所有地址信息addresses = Address.objects.filter(user=request.user, is_deleted=False)# 从Redis购物车中查询出被勾选的商品信息redis_conn = get_redis_connection('carts')redis_cart = redis_conn.hgetall('carts_%s' % user.id)cart_selected = redis_conn.smembers('selected_%s' % user.id)cart = {}for sku_id in cart_selected:cart[int(sku_id)] = int(redis_cart[sku_id])# 查询商品信息sku_list = []# 查询商品信息skus = SKU.objects.filter(id__in=cart.keys())for sku in skus:sku_list.append({'id':sku.id,'name':sku.name,'default_image_url':sku.default_image.url,'count': cart[sku.id],'price':sku.price})# 补充运费freight = Decimal('10.00')addresses_list = []for address in addresses:addresses_list.append({'id':address.id,'province':address.province.name,'city':address.city.name,'district':address.district.name,'place':address.place,'receiver':address.receiver,'mobile':address.mobile})# 渲染界面context = {'addresses': addresses_list,'skus': sku_list,'freight': freight,}return http.JsonResponse({'code':0,'errmsg':'ok','context':context})

二、提交订单 

2.1 创建订单数据库表

生成的订单数据要做持久化处理,而且需要在《我的订单》页面展示出来。

1. 订单数据库表分析

注意:

  • 订单号不再采用数据库自增主键,而是由后端生成。
  • 一个订单中可以有多个商品信息,订单基本信息和订单商品信息是一对多的关系。

 

 2. 订单模型类迁移建表

from django.db import models
from apps.goods.models import SKU
from apps.users.models import User, Address
from utils.models import BaseModelclass OrderInfo(BaseModel):"""订单信息"""PAY_METHODS_ENUM = {"CASH": 1,"ALIPAY": 2}PAY_METHOD_CHOICES = ((1, "货到付款"),(2, "支付宝"),)ORDER_STATUS_ENUM = {"UNPAID": 1,"UNSEND": 2,"UNRECEIVED": 3,"UNCOMMENT": 4,"FINISHED": 5}ORDER_STATUS_CHOICES = ((1, "待支付"),(2, "待发货"),(3, "待收货"),(4, "待评价"),(5, "已完成"),(6, "已取消"),)order_id = models.CharField(max_length=64, primary_key=True, verbose_name="订单号")user = models.ForeignKey(User, on_delete=models.PROTECT, verbose_name="下单用户")address = models.ForeignKey(Address, on_delete=models.PROTECT, verbose_name="收货地址")total_count = models.IntegerField(default=1, verbose_name="商品总数")total_amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="商品总金额")freight = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="运费")pay_method = models.SmallIntegerField(choices=PAY_METHOD_CHOICES, default=1, verbose_name="支付方式")status = models.SmallIntegerField(choices=ORDER_STATUS_CHOICES, default=1, verbose_name="订单状态")class Meta:db_table = "tb_order_info"verbose_name = '订单基本信息'verbose_name_plural = verbose_namedef __str__(self):return self.order_idclass OrderGoods(BaseModel):"""订单商品"""SCORE_CHOICES = ((0, '0分'),(1, '20分'),(2, '40分'),(3, '60分'),(4, '80分'),(5, '100分'),)order = models.ForeignKey(OrderInfo, related_name='skus', on_delete=models.CASCADE, verbose_name="订单")sku = models.ForeignKey(SKU, on_delete=models.PROTECT, verbose_name="订单商品")count = models.IntegerField(default=1, verbose_name="数量")price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="单价")comment = models.TextField(default="", verbose_name="评价信息")score = models.SmallIntegerField(choices=SCORE_CHOICES, default=5, verbose_name='满意度评分')is_anonymous = models.BooleanField(default=False, verbose_name='是否匿名评价')is_commented = models.BooleanField(default=False, verbose_name='是否评价了')class Meta:db_table = "tb_order_goods"verbose_name = '订单商品'verbose_name_plural = verbose_namedef __str__(self):return self.sku.name

2.1 保存订单基本信息和订单商品信息

1. 提交订单接口设计和定义

1.请求方式

选项方案
请求方法POST
请求地址/orders/commit/

2.请求参数:JSON

参数名类型是否必传说明
address_idint用户地址编号
pay_methodint用户支付方式

3.响应结果:JSON

字段说明
code状态码
errmsg错误信息
order_id订单编号

4.后端接口定义

class OrderCommitView(LoginRequiredJSONMixin, View):"""订单提交"""def post(self, request):"""保存订单信息和订单商品信息"""pass

提示:

  • 订单数据分为订单基本信息和订单商品信息,二者为一对多的关系。

  • 保存到订单的数据是从Redis购物车中的已勾选的商品信息。

2. 保存订单基本信息

class OrderCommitView(LoginRequiredJSONMixin, View):"""提交订单"""def post(self, request):"""保存订单信息和订单商品信息"""# 获取当前要保存的订单数据json_dict = json.loads(request.body.decode())address_id = json_dict.get('address_id')pay_method = json_dict.get('pay_method')# 校验参数if not all([address_id, pay_method]):return http.HttpResponseBadRequest('缺少必传参数')# 判断address_id是否合法try:address = Address.objects.get(id=address_id)except Exception:return http.HttpResponseBadRequest('参数address_id错误')# 判断pay_method是否合法if pay_method not in [OrderInfo.PAY_METHODS_ENUM['CASH'], OrderInfo.PAY_METHODS_ENUM['ALIPAY']]:return http.HttpResponseBadRequest('参数pay_method错误')# 获取登录用户user = request.user# 生成订单编号:年月日时分秒+用户编号order_id = timezone.localtime().strftime('%Y%m%d%H%M%S') + ('%09d' % user.id)# 保存订单基本信息 OrderInfo(一)order = OrderInfo.objects.create(order_id=order_id,user=user,address=address,total_count=0,total_amount=Decimal('0'),freight=Decimal('10.00'),pay_method=pay_method,status=OrderInfo.ORDER_STATUS_ENUM['UNPAID'] if pay_method == OrderInfo.PAY_METHODS_ENUM['ALIPAY'] elseOrderInfo.ORDER_STATUS_ENUM['UNSEND'])pass

3. 保存订单商品信息

class OrderCommitView(LoginRequiredJSONMixin, View):"""提交订单"""def post(self, request):"""保存订单信息和订单商品信息"""# 获取当前保存订单时需要的信息......# 保存订单基本信息 OrderInfo(一)......# 从redis读取购物车中被勾选的商品信息redis_conn = get_redis_connection('carts')redis_cart = redis_conn.hgetall('carts_%s' % user.id)selected = redis_conn.smembers('selected_%s' % user.id)carts = {}for sku_id in selected:carts[int(sku_id)] = int(redis_cart[sku_id])sku_ids = carts.keys()# 遍历购物车中被勾选的商品信息for sku_id in sku_ids:# 查询SKU信息sku = SKU.objects.get(id=sku_id)# 判断SKU库存sku_count = carts[sku.id]if sku_count > sku.stock:return http.JsonResponse({'code': 400, 'errmsg': '库存不足'})# SKU减少库存,增加销量sku.stock -= sku_countsku.sales += sku_countsku.save()# 保存订单商品信息 OrderGoods(多)OrderGoods.objects.create(order=order,sku=sku,count=sku_count,price=sku.price,)# 保存商品订单中总价和总数量order.total_count += sku_countorder.total_amount += (sku_count * sku.price)# 添加邮费和保存订单信息order.total_amount += order.freightorder.save()# 清除购物车中已结算的商品pl = redis_conn.pipeline()pl.hdel('carts_%s' % user.id, *selected)pl.srem('selected_%s' % user.id, *selected)pl.execute()# 响应提交订单结果return http.JsonResponse({'code': 0, 'errmsg': '下单成功', 'order_id': order.order_id})

 2.3 使用事务保存订单数据

重要提示:

  • 在保存订单数据时,涉及到多张表(OrderInfo、OrderGoods、SKU、SPU)的数据修改,对这些数据的修改应该是一个整体事务,即要么一起成功,要么一起失败。
  • Django中对于数据库的事务,默认每执行一句数据库操作,便会自动提交。所以我们需要在保存订单中自己控制数据库事务的执行流程。

1. Django中事务的使用

1.Django中事务的使用方案

  • 在Django中可以通过django.db.transaction模块提供的atomic来定义一个事务。

atomic提供两种方案实现事务:

装饰器用法:

  • from django.db import transaction@transaction.atomic
    def viewfunc(request):# 这些代码会在一个事务中执行......
    

with语句用法:

from django.db import transactiondef viewfunc(request):# 这部分代码不在事务中,会被Django自动提交......with transaction.atomic():# 这部分代码会在事务中执行......

2.事务方案的选择:

  • 装饰器用法: 整个视图中所有MySQL数据库的操作都看做一个事务,范围太大,不够灵活。而且无法直接作用于类视图。
  • with语句用法: 可以灵活的有选择性的把某些MySQL数据库的操作看做一个事务。而且不用关心视图的类型。
  • 综合考虑后我们选择 with语句实现事务

3.事务中的保存点:

  • 在Django中,还提供了保存点的支持,可以在事务中创建保存点来记录数据的特定状态,数据库出现错误时,可以回滚到数据保存点的状态。
from django.db import transaction# 创建保存点
save_id = transaction.savepoint()  
# 回滚到保存点
transaction.savepoint_rollback(save_id)
# 提交从保存点到当前状态的所有数据库事务操作
transaction.savepoint_commit(save_id)

2. 使用事务保存订单数据

class OrderCommitView(LoginRequiredJSONMixin, View):"""订单提交"""def post(self, request):"""保存订单信息和订单商品信息"""# 获取当前保存订单时需要的信息......# 显式的开启一个事务with transaction.atomic():# 创建事务保存点save_id = transaction.savepoint()# 暴力回滚try:# 保存订单基本信息 OrderInfo(一)order = OrderInfo.objects.create(order_id=order_id,user=user,address=address,total_count=0,total_amount=Decimal('0'),freight=Decimal('10.00'),pay_method=pay_method,status=OrderInfo.ORDER_STATUS_ENUM['UNPAID'] if pay_method == OrderInfo.PAY_METHODS_ENUM['ALIPAY'] elseOrderInfo.ORDER_STATUS_ENUM['UNSEND'])# 从redis读取购物车中被勾选的商品信息redis_conn = get_redis_connection('carts')redis_cart = redis_conn.hgetall('carts_%s' % user.id)selected = redis_conn.smembers('selected_%s' % user.id)carts = {}for sku_id in selected:carts[int(sku_id)] = int(redis_cart[sku_id])sku_ids = carts.keys()# 遍历购物车中被勾选的商品信息for sku_id in sku_ids:# 查询SKU信息sku = SKU.objects.get(id=sku_id)# 判断SKU库存sku_count = carts[sku.id]if sku_count > sku.stock:# 出错就回滚transaction.savepoint_rollback(save_id)return http.JsonResponse({'code': 400, 'errmsg': '库存不足'})# SKU减少库存,增加销量sku.stock -= sku_countsku.sales += sku_countsku.save()# 保存订单商品信息 OrderGoods(多)OrderGoods.objects.create(order=order,sku=sku,count=sku_count,price=sku.price,)# 保存商品订单中总价和总数量order.total_count += sku_countorder.total_amount += (sku_count * sku.price)# 添加邮费和保存订单信息order.total_amount += order.freightorder.save()except Exception as e:logger.error(e)transaction.savepoint_rollback(save_id)return http.JsonResponse({'code': 400, 'errmsg': '下单失败'})# 提交订单成功,显式的提交一次事务transaction.savepoint_commit(save_id)# 清除购物车中已结算的商品pl = redis_conn.pipeline()pl.hdel('carts_%s' % user.id, *selected)pl.srem('selected_%s' % user.id, *selected)pl.execute()# 响应提交订单结果return http.JsonResponse({'code': 0, 'errmsg': '下单成功', 'order_id': order.order_id})

 2.4 使用乐观锁并发下单

重要提示:

  • 在多个用户同时发起对同一个商品的下单请求时,先查询商品库存,再修改商品库存,会出现资源竞争问题,导致库存的最终结果出现异常。

1. 并发下单问题演示和解决方案

 

解决办法:

  • 悲观锁

    • 当查询某条记录时,即让数据库为该记录加锁,锁住记录后别人无法操作,使用类似如下语法

      select stock from tb_sku where id=1 for update;SKU.objects.select_for_update().get(id=1)
      
    • 悲观锁类似于我们在多线程资源竞争时添加的互斥锁,容易出现死锁现象,采用不多。

  • 乐观锁

    • 乐观锁并不是真实存在的锁,而是在更新的时候判断此时的库存是否是之前查询出的库存,如果相同,表示没人修改,可以更新库存,否则表示别人抢过资源,不再执行库存更新。类似如下操作

      update tb_sku set stock=2 where id=1 and stock=7;SKU.objects.filter(id=1, stock=7).update(stock=2)
      
  • 任务队列

    • 将下单的逻辑放到任务队列中(如celery),将并行转为串行,所有人排队下单。比如开启只有一个进程的Celery,一个订单一个订单的处理。

2. 使用乐观锁并发下单

思考:

  • 下单成功的条件是什么?
    • 首先库存大于购买量,然后更新库存和销量时原始库存没变。

结论:

  • 所以在用户库存满足的情况下,如果更新库存和销量时原始库存有变,那么继续给用户下单的机会。
class OrderCommitView(LoginRequiredJSONMixin, View):"""订单提交"""def post(self, request):"""保存订单信息和订单商品信息"""# 获取当前保存订单时需要的信息......# 显式的开启一个事务with transaction.atomic():# 创建事务保存点save_id = transaction.savepoint()# 暴力回滚try:# 保存订单基本信息 OrderInfo(一)order = OrderInfo.objects.create(order_id=order_id,user=user,address=address,total_count=0,total_amount=Decimal('0'),freight=Decimal('10.00'),pay_method=pay_method,status=OrderInfo.ORDER_STATUS_ENUM['UNPAID'] if pay_method == OrderInfo.PAY_METHODS_ENUM['ALIPAY'] elseOrderInfo.ORDER_STATUS_ENUM['UNSEND'])# 从redis读取购物车中被勾选的商品信息redis_conn = get_redis_connection('carts')redis_cart = redis_conn.hgetall('carts_%s' % user.id)selected = redis_conn.smembers('selected_%s' % user.id)carts = {}for sku_id in selected:carts[int(sku_id)] = int(redis_cart[sku_id])sku_ids = carts.keys()# 遍历购物车中被勾选的商品信息for sku_id in sku_ids:while True:# 查询SKU信息sku = SKU.objects.get(id=sku_id)# 读取原始库存origin_stock = sku.stockorigin_sales = sku.sales# 判断SKU库存sku_count = carts[sku.id]if sku_count > origin_stock:# 事务回滚transaction.savepoint_rollback(save_id)return http.JsonResponse({'code': 400, 'errmsg': '库存不足'})# 模拟延迟# import time# time.sleep(5)# SKU减少库存,增加销量# sku.stock -= sku_count# sku.sales += sku_count# sku.save()# 乐观锁更新库存和销量new_stock = origin_stock - sku_countnew_sales = origin_sales + sku_countresult = SKU.objects.filter(id=sku_id, stock=origin_stock).update(stock=new_stock, sales=new_sales)# 如果下单失败,但是库存足够时,继续下单,直到下单成功或者库存不足为止if result == 0:continue# 保存订单商品信息 OrderGoods(多)OrderGoods.objects.create(order=order,sku=sku,count=sku_count,price=sku.price,)# 保存商品订单中总价和总数量order.total_count += sku_countorder.total_amount += (sku_count * sku.price)# 下单成功或者失败就跳出循环break# 添加邮费和保存订单信息order.total_amount += order.freightorder.save()except Exception as e:logger.error(e)# 事务回滚transaction.savepoint_rollback(save_id)return http.JsonResponse({'code': 400, 'errmsg': '下单失败'})# 保存订单数据成功,显式的提交一次事务transaction.savepoint_commit(save_id)# 清除购物车中已结算的商品pl = redis_conn.pipeline()pl.hdel('carts_%s' % user.id, *selected)pl.srem('selected_%s' % user.id, *selected)pl.execute()# 响应提交订单结果return http.JsonResponse({'code': 0, 'errmsg': '下单成功', 'order_id': order.order_id})

3. MySQL事务隔离级别

  • 事务隔离级别指的是在处理同一个数据的多个事务中,一个事务修改数据后,其他事务何时能看到修改后的结果。

  • MySQL数据库事务隔离级别主要有四种:

    • Serializable:串行化,一个事务一个事务的执行。
    • Repeatable read:可重复读,无论其他事务是否修改并提交了数据,在这个事务中看到的数据值始终不受其他事务影响。
    • Read committed:读取已提交,其他事务提交了对数据的修改后,本事务就能读取到修改后的数据值。
    • Read uncommitted:读取未提交,其他事务只要修改了数据,即使未提交,本事务也能看到修改后的数据值。
    • MySQL数据库默认使用可重复读( Repeatable read)。
  • 使用乐观锁的时候,如果一个事务修改了库存并提交了事务,那其他的事务应该可以读取到修改后的数据值,所以不能使用可重复读的隔离级别,应该修改为读取已提交(Read committed)。

  • 修改方式:

sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf修改完成之后重启
sudo service mysql restart

 

 transaction-isolation=READ-COMMITTED

 


http://chatgpt.dhexx.cn/article/f6lITkdr.shtml

相关文章

电商系统-提交订单并发处理

在多个用户同时发起对一个商品的下单请求时,先查询商品库存,再修改商品库存,会出现资源竞争问题,导致库存的最终结果出现异常。 1、并发下单问题演示 每个不同的用户在程序上,我们可以理解成不同的线程,每…

Android Studio_Toast消息提醒

Android Studio_Toast消息提醒 1、Toast是Android系统提供的一种非常简洁的消息提醒方式,程序中可以使用它实现将短小的消息通知给用户,一点时间后自动消失,且不占用屏幕的任何空间。 2、Toast用法其实非常简单,通过静态方法make…

Vue 消息提示通知的几种方式汇总

Vue 消息提示通知组件(Message /Notification)是我们日常开发中经常使用的组件,它可用作与用户交互的反馈提示,信息提交成功、错误、操作警告等场景使用。原生JavaScript 提供了 alert、prompt、confirm 等方法 提示框1>Messag…

java信息提醒怎么实现_jsp怎么实现消息提醒

如果你是平台级别的系统,可以考虑消息队列的中间件,例如:阿里巴巴的rocketmq,用这个来做消息订阅与分发。 如果你只是简简单单的需要提示到web(jsp)页面,可以用js定时ajax访问后台,后台来确定是否有数据更新,无论这个数据是哪来的。 推荐课程:Java教程。 这里就使用在JSP页面…

RabbitMQ真延时队列实现消息提醒功能

RabbitMQ真延时队列实现消息提醒功能 一、需求场景 用户可以制定多个计划,同时可给该计划设置是否需要到点提醒,且中途可以取消提醒或修改提醒时间。 二、需要解决的问题 学习过rabbitmq的同学们都知道,通过TTL死信队列可以实现延时队列的…

企业微信 消息 html,企业微信怎么设置消息提醒

企业微信是一款非常不错的办公软件,用户加入企业群就能实时了解企业的动态。而且大家只需设置消息提醒,软件就会在第一时间通知你,不会让你错过任何重要的消息,下面小编为大家带来相关的设置教程。 方法/步骤分享: 1、…

vue websocket 新消息提醒

概述: 不是当前聊天,有其他消息来就通过2种方式接受到提醒。在连接的上下文中判断,符合条件的弹框,显示红点,此处调用了element弹框组件列表点击事件,红点消失列表显示,属性中包含小红点 前提…

html5载入提示音,html5新消息提示声音

【实例简介】 【实例截图】 【核心代码】HTML5手机声音提示 #chatBox{width:400px;border:1px solid #d3d3d3;margin:50px auto;} #chat {max-height:220px;overflow-y:auto;max-width:400px;} #chat > ul > li{padding:3px;clear:both;padding:4px;margin:10px 0px 5px …

【Android】消息提示notification

notification 1、notification消息提示 由Android系统来管理和维护的&#xff0c;因此用户可以随时进入查看。某些信息不需要用户马上处理&#xff0c;可以利用通知&#xff0c;即延迟消息&#xff0c;比如软件的更新、短信、新闻等。 2、消息包含的内容 3、代码 <Button…

消息提醒系统:设计模式与实现方案 (公告(通告)、消息、提醒等基本功能数据库表设计与实现)

参考地址&#xff1a; 公告(通告),消息,提醒等基本功能数据库表设计_DamonREN的博客-CSDN博客 多种消息提醒系统的设计模式、实现方案&#xff08;附功能截图表结构&#xff09;_黑夜的风的博客-CSDN博客_消息提醒 设计一个百万级的消息推送系统 - crossoverJie - 博客园 案…

android开发 app消息提醒功能,APP消息提醒设计:ios和android的最佳设计方案 – 25学堂...

我们都知道APP一项重要功能就是消息推送,那么通知栏的设计极大程度上反应了这个APP是否合理,那如何可以方便地为用户展示各种通知内容。也就将是我们APP设计师跟APP产品经理重点思考的问题?也要关注移动APP布局设计经验之道! 自从去年发布的iOS5中也引入了这一功能,以替代…

vue浏览器消息提示

vue浏览器消息提示 JS部分 //判断浏览器是否支持浏览器消息弹窗 suportNotify() {if (window.Notification) {// 支持console.log("支持" "Web Notifications API");//如果支持Web Notifications API&#xff0c;再判断浏览器是否支持弹出实例this.show…

html如何设置提示收到消息,从零开始实现一个消息提示框

引言 消息提示框在实际应用场景当中比较常见,最常用的就是element ui的消息提示框,我们通常都是直接使用它们,但是我们有没有尝试过去探究其实现原理,并自己动手实现呢?为了提升我们的个人能力和竞争力,我们可以尝试来实现这样一个消息提示框。 实现效果 我们来查看一下最…

多种消息提醒系统的设计模式、实现方案(附功能截图+表结构)

网站需要增加3种消息提醒系统。需要实现的功能如下&#xff1a; 1.评论提醒。 实现功能 他人回复自己后&#xff0c;右上角自动提醒“未阅读的新消息”的数量。 点击后&#xff0c;清空新消息的提示。 思路 这个是最简单的。在数据库查询&#xff1a; select count(id) …

前端实现实时消息提醒消息通知

需求&#xff1a;当用户收到待审批和待处理的消息后状态栏图标闪烁并进行弹窗提醒&#xff0c;点击消息跳转到指定的消息。 实现方式&#xff1a;web端c端。 说明&#xff1a; 客户不需要非常的及时的接收消息&#xff0c;所以未对接websocket协议&#xff0c;使用20秒刷新一…

Win11任务栏消息提醒功能如何开启教学

Win11任务栏消息提醒功能如何开启教学。我们可以将电脑的消息提示功能开启起来&#xff0c;这样我们接收到各种程序的消息通知时&#xff0c;都可以通过这个功能及时的给出提示。那么Win11任务栏消息提醒功能如何开启&#xff0c;我们接下来看看具体的操作方法吧。 设置方法&am…

用户登录登出功能实现

用户登录登出功能实现 一、功能需求分析 1. 登录退出功能分析 流程图 功能 登录页面登录功能退出功能 二、登录页面 1. 接口设计 接口说明 类目说明请求方法GETurl定义/user/login/参数格式无参数 返回结果 登录页面 2.后端代码 user/views.py代码&#xff1a; fro…

xubuntu系统偶发自动登出

项目场景&#xff1a; 系统&#xff1a;xubuntu-16.04.3-desktop 问题描述 使用xubuntu系统期间&#xff0c;在root用户下进行相关开发&#xff0c;突然系统会回到普通用户登录界面&#xff0c;需要输入密码进入到普通用户下   它会终止所有打开的应用程序和进程&#xff0…

Spring Security OAuth2 单点登录和登出

文章目录 1. 单点登录1.1 使用内存保存客户端和用户信息1.1.1 认证中心 auth-server1.1.2 子系统 service-11.1.3 测试 1.2 使用数据库保存客户端和用户信息1.3 单点登录流程1.2.1 请求授权码&#xff0c;判断未登录&#xff0c;重定向登录页1.2.2 登录成功&#xff0c;重定向继…

OneNote的正确登出方式

1. 账号已经是登录状态 点击oneNote右上角账户信息 》 注销按钮 &#xff08;退出登录&#xff0c;注意要确保笔记已经同步到云端&#xff09; 》会弹出以下提示窗口 》点击是 2. 关闭笔记本 点击左上角 》 文件 》信息》选中自己的笔记本 》设置 》关闭&#xff08;可防止…