.NETCore WebApi中的缓存机制

一、说明

.NET Core WebApi基础入门项目源码下载

  作为WebApi接口,性能效率是必不可少的,每次的访问请求,数据库读取、业务逻辑处理都会或多或少的花费时间,特别是一些共用的公有数据,频繁访问且大量重复;针对这样的情况我们就可以考虑通过缓存放置到内存存储中,以空间换取时间的提升,可以提高应用程序的效率;关于缓存的方法有【Cache、Session、Cookie等】,还有功能比较强大的缓存方法,如:Redis等。

  本文主要讲述MemoryCache、Session、Cookie这三种缓存方法。

二、缓存方法

2.0、建立一个独立的类库用来编写通用的工具方法

2.0.1、建立独立库的介绍

  建立一个独立的类库编写通用的工具方法内容:优点是:

①一处编写多处复用;

②方便管理维护和更新;

③职责分明,实现工具与业务解耦;

2.1、建立一个独立类库的操作

2.1、Cache【MemoryCache】

2.1.1、Cache介绍

  .Net Core中虽然没有现成的Cache方法;但微软官方提供了内存缓存(MemoryCache)的工具包。Cache的应用范围:

①Cache用于在Http请求期间保存页面或者数据。

②Cache的使用可以大大的提高整个应用程序的效率。

③它允许将频繁访问的服务器资源存储在内存中,当用户发出相同的请求后,服务器不是再次处理而是将Cache中保存的数据直接返回给用户。

④可以看出Cache节省的是时间—服务器处理时间。

⑤Cache实例是每一个应用程序专有的,其生命周期==该应用程序周期。

  Cache:它存储于 服务器的内存中,允许您自定义如何缓存项以及将它们缓存多长时间。例如,当缺乏系统内存时,缓存会自动移除很少使用的或优先级较低的项以释放内存。该技术 也称为清理,这是缓存确保过期数据不使用宝贵的服务器资源的方式之一。它不与会话相关,所以它是多会话共享的,因此使用它可以提高网站性能,但是可能泄露 用户的安全信息,还由于在服务器缺乏内存时可能会自动移除Cache因此需要在每次获取数据时检测该Cache项是否还存在。

  Cache的关键特性有:存储于服务器内存中,与会话无关,根据服务器内存资源的状况随时可能被丢弃,不被序列化,不发生服务器-客户端数据传输。

2.1.2、Cache实现

①安装【Microsoft.Extensions.Caching.Memory】的Nuget资源包

 ②编写CacheHelper类

/***
*	Title:".NET Core WebApi" 项目
*		主题:内存缓存帮助类
*	Description:
*		功能:
*		    1、验证缓存项是否存在
*		    2、添加缓存
*		    3、删除缓存(删除指定缓存、批量删除缓存、删除所有缓存)
*		    4、获取缓存(获取指定缓存、获取所有的缓存键、获取缓存集合)
*	Date:2021
*	Version:0.1版本
*	Author:Coffee
*	Modify Recoder:
*/

using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;

namespace WebApiUtils
{
    public class CacheHelper
    {
        #region   基础参数
        //内存缓存
        private static readonly MemoryCache _cache = new MemoryCache(new MemoryCacheOptions());

        #endregion 




        /// <summary>
        /// 验证缓存项是否存在
        /// </summary>
        /// <param name="key">缓存Key</param>
        /// <returns>返回结果(true:存在)</returns>
        public static bool Exists(string key)
        {
            if (key == null)
                throw new ArgumentNullException(nameof(key));
            return _cache.TryGetValue(key, out _);
        }


        /// <summary>
        /// 添加缓存
        /// </summary>
        /// <param name="key">缓存Key</param>
        /// <param name="value">缓存Value</param>
        /// <returns>返回结果(true:存在)</returns>
        public static bool Set(string key, object value)
        {
            if (string.IsNullOrEmpty(key))
                throw new ArgumentNullException(nameof(key));
            if (value == null)
                throw new ArgumentNullException(nameof(value));

            _cache.Set(key, value);

            return Exists(key);
        }

        /// <summary>
        /// 添加缓存
        /// </summary>
        /// <param name="key">缓存Key</param>
        /// <param name="value">缓存Value</param>
        /// <param name="expiresIn">缓存时长</param>
        /// <param name="isSliding">是否滑动过期(如果在过期时间内有操作,则以当前时间点延长过期时间)</param>
        /// <returns>返回结果(true:存在)</returns>
        public static bool Set(string key, object value, TimeSpan expiresIn, bool isSliding = false)
        {
            if (string.IsNullOrEmpty(key))
                throw new ArgumentNullException(nameof(key));
            if (value == null)
                throw new ArgumentNullException(nameof(value));

            _cache.Set(key, value,
             isSliding
                 ? new MemoryCacheEntryOptions().SetSlidingExpiration(expiresIn)
                 : new MemoryCacheEntryOptions().SetAbsoluteExpiration(expiresIn));


            return Exists(key);
        }


        #region 删除缓存

        /// <summary>
        /// 删除缓存
        /// </summary>
        /// <param name="key">缓存Key</param>
        /// <returns></returns>
        public static void Remove(string key)
        {
            if (string.IsNullOrEmpty(key))
                throw new ArgumentNullException(nameof(key));

            _cache.Remove(key);

        }

        /// <summary>
        /// 删除匹配到的缓存
        /// </summary>
        /// <param name="key">缓存key</param>
        /// <returns></returns>
        public static void RemoveCacheRegex(string key)
        {
            IList<string> list = SearchCacheRegex(key);
            foreach (var s in list)
            {
                Remove(s);
            }
        }

        /// <summary>
        /// 批量删除缓存
        /// </summary>
        /// <returns></returns>
        public static void RemoveAll(IEnumerable<string> keys)
        {
            if (keys == null)
                throw new ArgumentNullException(nameof(keys));

            keys.ToList().ForEach(item => _cache.Remove(item));

        }

        /// <summary>
        /// 删除所有缓存
        /// </summary>
        /// <returns></returns>
        public static void RemoveCacheAll()
        {
            var tmp = GetCacheKeys();
            foreach (var s in tmp)
            {
               Remove(s);
            }
        }

        #endregion


        #region 获取缓存

        /// <summary>
        /// 获取缓存
        /// </summary>
        /// <param name="key">缓存Key</param>
        /// <returns>返回对应类型</returns>
        public static T Get<T>(string key) where T : class
        {
            if (string.IsNullOrEmpty(key))
                throw new ArgumentNullException(nameof(key));

            return _cache.Get(key) as T;
        }

        /// <summary>
        /// 获取缓存
        /// </summary>
        /// <param name="key">缓存Key</param>
        /// <returns>返回object</returns>
        public static object Get(string key)
        {
            if (string.IsNullOrEmpty(key))
                throw new ArgumentNullException(nameof(key));

            return _cache.Get(key);
        }

        /// <summary>
        /// 搜索匹配到的缓存
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static IList<string> SearchCacheRegex(string key)
        {
            var cacheKeys = GetCacheKeys();
            var tmp = cacheKeys.Where(k => Regex.IsMatch(k, key)).ToList();
            return tmp.AsReadOnly();
        }

        /// <summary>
        /// 获取所有缓存键
        /// </summary>
        /// <returns>返回所有的缓存键列表</returns>
        public static List<string> GetCacheKeys()
        {
            const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic;
            var entries = _cache.GetType().GetField("_entries", flags).GetValue(_cache);
            var cacheItems = entries as IDictionary;
            var keys = new List<string>();
            if (cacheItems == null) return keys;
            foreach (DictionaryEntry cacheItem in cacheItems)
            {
                keys.Add(cacheItem.Key.ToString());
            }
            return keys;
        }

        /// <summary>
        /// 获取缓存集合
        /// </summary>
        /// <param name="keys">缓存Key集合</param>
        /// <returns>返回字典</returns>
        public static IDictionary<string, object> GetAll(IEnumerable<string> keys)
        {
            if (keys == null)
                throw new ArgumentNullException(nameof(keys));

            var dict = new Dictionary<string, object>();
            keys.ToList().ForEach(item => dict.Add(item, _cache.Get(item)));
            return dict;
        }



        #endregion

       

    }//Class_end

}

 ③在Controllers文件夹下新建一个Test文件夹提供做测试【编写CacheHelper测试类】

/***
*	Title:".NET Core WebApi" 项目
*		主题:测试内存缓存
*	Description:
*		功能:
*	Date:2021
*	Version:0.1版本
*	Author:Coffee
*	Modify Recoder:
*/

using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using WebApiUtils;

namespace WebApi_Learn.Controllers.Test
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class Test_MermoryCache
    {

        /// <summary>
        /// 键是否存在
        /// </summary>
        /// <param name="key">键</param>
        /// <returns>返回结果(true:表示成功)</returns>
        [HttpGet]
        public bool IsExistCache(string key)
        {
            return CacheHelper.Exists(key);
        }

        /// <summary>
        /// 添加缓存没有时间周期
        /// </summary>
        /// <param name="key">键</param>
        /// <param name="value">值</param>
        /// <returns>返回结果(true:表示成功)</returns>
        [HttpGet,Route("AddMemeryCache")]
        public bool AddCacheNoTime(string key, string value)
        {
           return  CacheHelper.Set(key,value);
        }

        /// <summary>
        /// 添加缓存有时间周期
        /// </summary>
        /// <param name="key">键</param>
        /// <param name="value">值</param>
        /// <param name="secondNumber">缓存秒数</param>
        /// <returns>返回结果(true:表示成功)</returns>
        [HttpGet, Route("AddMemeryCache")]
        public bool AddCacheHasTime(string key, string value, int secondNumber)
        {
            return CacheHelper.Set(key, value, new TimeSpan(0,0,secondNumber), false);
        }

        /// <summary>
        /// 删除缓存
        /// </summary>
        /// <param name="key">键</param>
        /// <returns></returns>
        [HttpGet]
        public void DeleteCache(string key)
        {
             CacheHelper.Remove(key);   
        }

        /// <summary>
        /// 删除所有缓存
        /// </summary>
        [HttpGet]
        public void DeleteAllCache()
        {
            CacheHelper.RemoveCacheAll();
        }

        /// <summary>
        /// 获取到缓存
        /// </summary>
        /// <param name="key">键</param>
        /// <returns>返回结果(true:表示成功)</returns>
        [HttpGet]
        public string GetCache(string key)
        {
            try
            {
               return  CacheHelper.Get(key).ToString();
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }

        /// <summary>
        /// 获取到所有的缓存
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        public List<string> GetAllCache()
        {
           return CacheHelper.GetCacheKeys();
        }


    }//Class_end
}

④运行验证

1、运行程序

2、选择添加有时间周期的缓存

 3、过了一段时间后查看缓存是否存在

2.2、Session

2.2.1、Session介绍

Session的引用范围:

①Session用来保存每一个用户的专有信息。

②Session的生存期是用户持续请求时间加上一段时间(一般是20分钟左右)。

③Session信息是保存在Web服务器内存中的,保存数据量可大可小。
由于用户停止使用应用程序之后它仍在内存中存留一段时间,因此这种方法效率较低。

  Session为当前用户会话提供信息。还提供对可用于存储信息的会话范围的缓存的访问,以及控制如何管理会话的方法。它存储在服务器的内存中,因此与在数据库中存储和检索信息相比,它的执行速度更快。与不特定于单个用户会话的应用程 序状态不同,会话状态应用于单个的用户和会话。因此,应用程序状态非常适合存储那些数量少、随用户的变化而变化的常用数据。而且由于其不发生服务器-客户 端数据传输,Session还适合存储关于用户的安全数据,如购物车信息

  Session的关键特性有:存储于服务器内存中,与会话相关,在会话的整个生存期中存在即不会被主动丢弃,不被序列化,不发生服务器-客户端数据传输。

 2.2.2、Session实现

①安装【Microsoft.AspNetCore.Session】和【Microsoft.AspNetCore.Http.Extensions】的Nuget包

 ②编写SessionHelper类

1、注册Session

 public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            #region Session
            //注册HttpContextAccessor对象,用以获取HttpContext信息
            services.AddHttpContextAccessor();

            //启用session之前必须先添加内存
            services.AddDistributedMemoryCache();
            
            //注册Session
            services.AddSession(options =>
            {
                options.Cookie.Name = "WebApi_Learn.Session";//名称随便写(一般填写项目名称即可)
                options.IdleTimeout = TimeSpan.FromSeconds(2000);//设置session的过期时间
                options.Cookie.HttpOnly = true;//设置在浏览器不能通过js获得该cookie的值,实际场景根据自身需要
            });

            #endregion
           
        }

2、使用Session


            #region   Session
            //UseSession配置在UseMvc/UseEndpoints之前
            app.UseSession();

            #endregion

 3、编写SessionHelper类

/***
*	Title:".NET Core WebApi" 项目
*		主题:Session帮助类
*	Description:
*		功能:
*		    1、设置Session
*		    2、获取Session
*		    3、删除Session
*	Date:2021
*	Version:0.1版本
*	Author:Coffee
*	Modify Recoder:
*/

using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Text;

namespace WebApiUtils
{
    public class SessionHelper
    {

        /// <summary>
        /// 设置Session
        /// </summary>
        /// <param name="httpContext">httpContext内容</param>
        /// <param name="key">键</param>
        /// <param name="value">值</param>
        public static void SetSession(HttpContext httpContext, string key, string value)
        {
            httpContext.Session.SetString(key, value);
        }


        /// <summary>
        /// 获取Session
        /// </summary>
        /// <param name="httpContext">httpContext内容</param>
        /// <param name="key">键</param>
        /// <param name="defaultValue">默认值</param>
        /// <returns>返回对应的值</returns>
        public static string GetSession(HttpContext httpContext, string key, string defaultValue = "")
        {
            string value = httpContext.Session.GetString(key);
            if (string.IsNullOrEmpty(value))
            {
                value = defaultValue;
            }
            return value;
        }


        /// <summary>
        /// 删除Session
        /// </summary>
        /// <param name="httpContext">httpContext内容</param>
        /// <param name="key">键</param>
        public static void DeleteSession(HttpContext httpContext, string key)
        {
            httpContext.Session.Remove(key);
        }


    }//Class_end

}

③在Controllers文件夹下新建一个Test文件夹提供做测试【编写SessionHelper测试类】

/***
*	Title:".NET Core WebApi" 项目
*		主题:测试服务器缓存(Session)
*	Description:
*		功能:XXX
*	Date:2021
*	Version:0.1版本
*	Author:Coffee
*	Modify Recoder:
*/

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using WebApiUtils;

namespace WebApi_Learn.Controllers.Test
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class Test_Session
    {
        #region   基础参数

        //HttpContext内容
        private HttpContext _httpContext = ResponseHelper.HttpCurrent;

        #endregion 


        /// <summary>
        /// 设置Session
        /// </summary>
        /// <param name="key">键</param>
        /// <param name="value">值</param>
        /// <returns>返回当前设置的键值对</returns>
        [HttpGet]
        public ActionResult<IEnumerable<string>> SetSession(string key,string value)
        {
            SessionHelper.SetSession(_httpContext, key,value);

            return new string[] { key, value };
        }


        /// <summary>
        /// 根据键获取值
        /// </summary>
        /// <param name="key">键</param>
        /// <returns>获取键对应的值</returns>
        [HttpGet("key")]
        public ActionResult<string> GetSession(string key)
        {
            string value = SessionHelper.GetSession(_httpContext, key,"当前数据不存在!!!");
            return value;
        }

        /// <summary>
        /// 删除Session
        /// </summary>
        /// <param name="key">键</param>
        [HttpGet]
        public void DeleteSession(string key)
        {
            SessionHelper.DeleteSession(_httpContext, key);
        }


    }//Class_end
}

④运行测试

1、运行程序

 2、设置Session

  3、查看缓存是否存在

2.3、Cookie

2.3.1、Cookie介绍

Cookie的应用范围:

①Cookie用来保存客户浏览器请求服务器页面的请求信息。

②我们可以存放非敏感的用户信息,保存时间可以根据需要设置。

③如果没有设置Cookie失效日期,它的生命周期保存到关闭浏览器为止。

④Cookie对象的Expires属性设置为MinValue表示永不过期。

⑤Cookie存储的数据量受限制,大多数的浏览器为4K因此不要存放大数据。

⑥由于并非所有的浏览器都支持Cookie,数据将以明文的形式保存在客户端。

  Cookie:Cookie 提供了一种在 Web 应用程序中存储用户特定信息的方法。例如,当用户访问您的站点时,您可以使用 Cookie 存储用户首选项或其他信息。当该用户再次访问您的网站时,应用程序便可以检索以前存储的信息。在开发人员以编程方式设置Cookie时,需要将自己希望保 存的数据序列化为字符串(并且要注意,很多浏览器对Cookie有4096字节的限制)然后进行设置

      Cookie的关键特性有:存储于客户端硬盘上,与用户相关,在一定时间内持久化存储,可以跨浏览器共享数据,需要被序列化,发生服务器-客户端数据传输。

2.3.2、Cookie实现

①Cookie本身在.NET Core中已经支持,所以不需要引入啥NuGet包。

②编写CookieHelper类。

/***
*	Title:".NET Core WebApi" 项目
*		主题:Cookie帮助类
*	Description:
*		功能:
*		    1、设置Cookie
*		    2、删除Cookie
*		    3、获取Cookie
*	Date:2021
*	Version:0.1版本
*	Author:Coffee
*	Modify Recoder:
*/

using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Text;

namespace WebApiUtils
{
    public class CookieHelper
    {

        /// <summary>
        /// 设置Cookie
        /// </summary>
        /// <param name="httpContext">httpContext内容</param>
        /// <param name="key">键</param>
        /// <param name="value">值</param>  
        /// <param name="minutes">过期时长(单位:秒)</param>      
        public static void SetCookie(HttpContext httpContext, string key, string value, int seconds = 10)
        {
            httpContext.Response.Cookies.Append(key, value, new CookieOptions
            {
                Expires = DateTime.Now.AddSeconds(seconds)
            });
        }

        /// <summary>
        /// 获取Cookie
        /// </summary>
        /// <param name="httpContext">httpContext内容</param>
        /// <param name="key">键</param>
        /// <returns>返回键对应的值</returns>
        public static string GetCookie(HttpContext httpContext, string key, string defaultValue = "")
        {
            string value = string.Empty;
            httpContext.Request.Cookies.TryGetValue(key, out value);
            if (string.IsNullOrEmpty(value))
            {
                value = defaultValue;
            }
            return value;
        }


        /// <summary>
        /// 删除Cookie
        /// </summary>
        /// <param name="httpContext">httpContext内容</param>
        /// <param name="key">键</param>
        public static void DeleteCookie(HttpContext httpContext, string key)
        {
            httpContext.Response.Cookies.Delete(key);
        }

      


    }//Class_end

}

③在Controllers文件夹下新建一个Test文件夹提供做测试【编写CookieHelper测试类】

/***
*	Title:".NET Core WebApi" 项目
*		主题:测试服务器缓存(Cookie)
*	Description:
*		功能:XXX
*	Date:2021
*	Version:0.1版本
*	Author:Coffee
*	Modify Recoder:
*/

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using WebApiUtils;

namespace WebApi_Learn.Controllers.Test
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class Test_Cookie
    {
        #region   基础参数
        //HttpContext内容
        private HttpContext _httpContext = ResponseHelper.HttpCurrent;

        #endregion 

        /// <summary>
        /// 设置Cookie
        /// </summary>
        /// <param name="key">键</param>
        /// <param name="value">值</param>
        /// <param name="timeIntervalSeconds">时间间隔(单位:秒)</param>
        [HttpGet]
        public void SetCookie(string key, string value,int timeIntervalSeconds)
        {
            CookieHelper.SetCookie(_httpContext,key,value, timeIntervalSeconds);
        }

        /// <summary>
        /// 获取Cookie
        /// </summary>
        /// <param name="key">键</param>
        /// <returns>返回键对应的值</returns>
        [HttpGet]
        public string GetCookie(string key)
        {
           return CookieHelper.GetCookie(_httpContext,key,$"当前:{key} 不存在");
        }


        /// <summary>
        /// 删除Cookie
        /// </summary>
        /// <param name="key">键</param>
        [HttpGet]
        public void DeleteCookie(string key)
        {
            CookieHelper.DeleteCookie(_httpContext,key);
        }

    }//Class_end
}

④运行测试

1、运行程序

 2、设置Cookie

3、过了一会查看Cookie是否存在

 三、响应帮助类

3.1、编写响应帮助类

/***
*	Title:".NET Core WebApi" 项目
*		主题:响应帮助类
*	Description:
*		功能:
*		    1、异常处理回调
*		    2、统一请求页面实体
*	Date:2021
*	Version:0.1版本
*	Author:Coffee
*	Modify Recoder:
*/

using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;

namespace WebApiUtils
{
    public class ResponseHelper
    {
        #region   基础参数
        //服务接口
        public static IServiceProvider serviceProvider;

        #endregion 

        /// <summary>
        /// 异常处理回调
        /// </summary>
        /// <param name="statusCode">html状态码</param>
        /// <param name="msg">消息</param>
        /// <returns></returns>
        public static Task HandleExceptionAsync(int statusCode, string msg)
        {
            var data = new { code = statusCode, msg = msg };
            string text = JsonConvert.SerializeObject(data);

            var response = HttpCurrent.Response;
            if (string.IsNullOrEmpty(response.ContentType))
            {
                //跨域的时候注意,不带header没法接收回调
                response.Headers.Add("Access-Control-Allow-Origin", "*");
                response.Headers.Add("Access-Control-Allow-Credentials", "true");
                //因为这个是json
                response.ContentType = "application/json;charset=utf-8";
                response.StatusCode = 200;
                response.ContentLength = text.Length;
                return response.WriteAsync(text);
            }
            else
            {
                return response.WriteAsync(text);
            }
        }


        /// <summary>
        /// 统一请求页面实体
        /// </summary>
        public static HttpContext HttpCurrent
        {
            get
            {
                object factory = serviceProvider.GetService(typeof(IHttpContextAccessor));
                HttpContext context = ((IHttpContextAccessor)factory).HttpContext;
                return context;
            }
        }

    }//Class_end

}

3.2、注册注册HttpContextAccessor对象

在Startup的ConfigureServices方法中注册HttpContextAccessor对象

            //注册HttpContextAccessor对象,用以获取HttpContext信息
            services.AddHttpContextAccessor();

3.3、使用程序服务

 在Startup的Configure中使用程序服务

            //使用服务
            WebApiUtils.ResponseHelper.serviceProvider = app.ApplicationServices;

 参考文章:

net core Webapi基础工程搭建(五)

关于缓存中Cookie,Session,Cache的使用

  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

牛奶咖啡13

我们一起来让这个世界有趣一点…

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值