数据验证随想

前言

  在方法中,为了保证数据的有效性,难免对它做检查,在参数多的时候,if判断就会很多。示例代码如下:

        public ActionResult Login(string uid, string pwd)
        {
            ResultDTO rdto = new ResultDTO();
            if (uid == null || uid.Length == 0)
            {
                rdto.Message = "用户名不能为空";
                return this.Json(rdto);
            }
            if (pwd == null || pwd.Length == 0)
            {
                rdto.Message = "密码不能为空";
                return this.Json(rdto);
            }
             ...
        }

  这仅仅是2个字段进行非空验证,如果是字段多,而且验证方式更多,则难免也写N个if判断。

  MVC中提供了验证机制,在实体上添加Attrbute即可指明验证参数的方法。但是很多时候参数是不固定的呀,多变的,比如说查询条件,少则三四个,多则十几个,更多的也有。所以在验证参数这一块,还不知道有什么方法可以简化我们的工作量,如果有朋友知道,还请告知,先谢过。

  来说说楼主的方法,目前仅在猜想中,未应用于任何项目。

初步方案

  先看看楼主的调用方式:

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
namespace MacRead
{
    class Program
    {
        static void Main(string[] args)
        {
            //参数格式:模仿MVC中的FormValueCollection,或Request["xxx"]
            var form = new NameValueCollection();
            form["UserName"] = "admin";
            form["password"] = "*********";
            form["Sex"] = "2";
            //form["Sex"] = null;
            form["Email"] = "304885433@qq.com";
            /*
            指明必须验证的字段
            验证数据 自动填充Model
             */
            var Request = HttpContext.Current.Request;
            var v = ValidateHelp.BeginEntity<User>()
                 .Require("UserName", "password", "Email", "Sex")
                 .IsNullOrEmpty(form["UserName"], "用户名不能为空")
                 .IsNullOrEmpty(form["password"], "密码不能为空", "password")
                 .IsInt(form["Sex"], "无法识别性别", "Sex")
                 .IsEmail(form["Email"], "邮箱地址错误", "Email");
            if (v.IsValid)
            {
                Console.WriteLine("验证通过");
                var m = v.Entity as User;
            }
            else
            {
                Console.WriteLine("验证未通过 信息:" + v.ErrorMsg);
            }

            /*
            验证数据 自动填充Dictionary
             */
            form["Email"] = "304885433";
            v = ValidateHelp.BeginDic()
                 .Require("UserName", "password", "Email", "Sex")
                 .IsNullOrEmpty(form["UserName"], "用户名不能为空", "UserName")
                 .IsNullOrEmpty(form["password"], "密码不能为空", "password")
                 .IsInt(form["Sex"], "无法识别性别", "Sex")
                 .IsEmail(form["Email"], "邮箱地址错误", "Email");
            if (v.IsValid)
            {
                Console.WriteLine("验证通过");
            }
            else
            {
                Console.WriteLine("验证未通过 信息:" + v.ErrorMsg);
            }
            foreach (var d in v.Data)
            {
                Console.WriteLine("{0}={1}", d.Key, d.Value);
            }
        }
    }
    public class User
    {
        public string UserName { get; set; }
        public string password { get; set; }
        public int Sex { get; set; }
        public string Email { get; set; }
    }
}

  定义了一个 ValidateHelp 对象,在初始化的时候,可以选择数据容器。

  解释一下,为什么需要数据容器。我们在方法开始对参数做验证,验证通过后必然是要去使用它,使用时难免需要类型转换,所以干脆在验证的时候去指明一个数据容器,在数据验证通过后,将数据填充到指定的容器中即可,验证完毕后,直接取出来使用。

  看看其中一个验证方法的定义。

        public ValidateHelp IsNullOrEmpty(string s, string msg = null, string propertyName = null)

  s:被验证的字符串

  msg:验证失败的提示消息

  propertyName:验证成功后保存的属性名称,当数据容器为Dictionary时为Key,当数据容器为Model时为属性名。

  所以楼主的目的就是验证失败时,得到错误信息,验证成功,能自动把类型变换后的值保存。

反射的优化方案

  由于上面说了,目前还在猜想阶段,在对实体进行赋值时,使用的是反射。

  性能要求不高的场景下,用反射即可,要求高的时候,推荐一个朋友写的库,在整个软件生命周期,调用次数达到1w以上,性能十分强劲,大家可以看看,《Literacy 快速反射读写对象属性,字段》

说说Require

  楼主在原来的项目中做查询,条件总数大概有十几个,其中4-5个是必需的,其他为null时可以忽略,加入Require方法,目的则是指明部分属性是不可以为null的。在上面的实例中,对于Sex字段验证时,该值为null,最后依然验证通过。

  源码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace MacRead
{
    public class ValidateHelp
    {
        #region 属性
        //数据容器字典
        public Dictionary<string, object> Data { get; private set; }
        //数据容器实体
        public Object Entity { get; private set; }
        //错误信息
        public string ErrorMsg { get; private set; }
        // true:验证通过 false:验证未通过
        public bool IsValid { get; private set; }
        #endregion

        #region 字段

        //数据容器实体类型
        private Type EntityType;
        //必需的字段信息
        private string[] RequiredProperty;
        #endregion

        #region 静态方法 得到对象实例
        public static ValidateHelp Begin()
        {
            return new ValidateHelp();
        }

        public static ValidateHelp BeginDic()
        {
            var v = new ValidateHelp();
            v.Data = new Dictionary<string, object>();
            return v;
        }

        public static ValidateHelp BeginEntity<T>()
        {
            var v = new ValidateHelp();
            v.EntityType = typeof(T);
            v.Entity = Activator.CreateInstance(v.EntityType);
            return v;
        }
        #endregion

        #region 辅助方法
        public object this[string key]
        {
            get
            {
                object o;
                if (Data.TryGetValue(key, out o)) return o;
                return null;
            }
        }

        /// <summary>
        /// 指明必需的属性 不允许为null
        /// </summary>
        /// <param name="requiredProperty"></param>
        /// <returns></returns>
        public ValidateHelp Require(params string[] requiredProperty)
        {
            if (this.RequiredProperty != null) throw new Exception("Require can‘t be called multiple times ");
            this.RequiredProperty = requiredProperty;
            return this;
        }
        #endregion

        #region 私有方法
        private ValidateHelp()
        {
            IsValid = true;//默认验证通过
        }

        /// <summary>
        /// 数据验证不通过
        /// </summary>
        /// <param name="msg">错误信息提示</param>
        /// <returns></returns>
        private ValidateHelp SetInValid(string msg)
        {
            IsValid = false;
            ErrorMsg = msg;
            return this;
        }

        /// <summary>
        /// 数据验证通过
        /// </summary>
        /// <param name="value">数据</param>
        /// <param name="propertyName">属性名</param>
        /// <returns></returns>
        private ValidateHelp SetValid(object value, string propertyName)
        {
            if (!IsValid) IsValid = true;
            if (propertyName != null && propertyName.Length > 0)
            {
                if (Data != null)
                {
                    Data[propertyName] = value;
                }
                else if (Entity != null)
                {
                    var p = Entity.GetType().GetProperty(propertyName);
                    //为对象属性赋值
                    p.SetValue(Entity, value, null);
                }
            }
            return this;
        }

        /// <summary>
        /// 验证检查 返回True 终止验证,false 继续验证
        /// </summary>
        /// <param name="s">输入值</param>
        /// <param name="propertyName">属性值</param>
        /// <returns></returns>
        private bool Interrupt(string s, string propertyName)
        {
            //验证无效,直接返回
            if (!IsValid) return true;
            /*继续验证条件 
             * s非空
             * 未指明属性名
             * 未定义必需的属性
             */
            if (s != null ||
                propertyName == null ||
                propertyName.Length == 0 ||
                RequiredProperty == null ||
                RequiredProperty.Length == 0) return false;
            //必需属性数组中包含属性时,
            var i = Array.IndexOf(RequiredProperty, propertyName);
            if (i > -1) // 属性还被指定必需,则直接判定为无效,终止验证
            {
                IsValid = false;
                ErrorMsg = propertyName + " is null";
            }
            // 属性为null时 中断验证
            return true;
        }
        #endregion

        #region 验证方法
        public ValidateHelp IsNullOrEmpty(string s, string msg = null, string propertyName = null)
        {
            if (Interrupt(s, propertyName)) return this;
            if (s == null || s.Length == 0)
            {
                return SetInValid(msg);
            }
            return SetValid(s, propertyName);
        }

        public ValidateHelp IsInt(string s, string msg = null, string propertyName = null)
        {
            if (Interrupt(s, propertyName)) return this;
            int i;
            if (!int.TryParse(s, out i))
            {
                return SetInValid(msg);
            }
            return SetValid(i, propertyName);
        }

        public ValidateHelp IsDouble(string s, string msg = null, string propertyName = null)
        {
            if (Interrupt(s, propertyName)) return this;
            double i;
            if (!double.TryParse(s, out i))
            {
                return SetInValid(msg);
            }
            return SetValid(i, propertyName);
        }

        public ValidateHelp IsLong(string s, string msg = null, string propertyName = null)
        {
            if (Interrupt(s, propertyName)) return this;
            long i;
            if (!long.TryParse(s, out i))
            {
                return SetInValid(msg);
            }
            return SetValid(i, propertyName);
        }

        public ValidateHelp IsFloat(string s, string msg = null, string propertyName = null)
        {
            if (Interrupt(s, propertyName)) return this;
            float i;
            if (!float.TryParse(s, out i))
            {
                return SetInValid(msg);
            }
            return SetValid(i, propertyName);
        }

        public ValidateHelp IsBool(string s, string msg = null, string propertyName = null)
        {
            if (Interrupt(s, propertyName)) return this;
            bool i;
            if (!bool.TryParse(s, out i))
            {
                return SetInValid(msg);
            }
            return SetValid(i, propertyName);
        }

        public ValidateHelp IsDateTime(string s, string msg = null, string propertyName = null)
        {
            if (Interrupt(s, propertyName)) return this;
            DateTime i;
            if (!DateTime.TryParse(s, out i))
            {
                return SetInValid(msg);
            }
            return SetValid(i, propertyName);
        }

        public ValidateHelp IsEnum<T>(string s, string msg = null, string propertyName = null) where T : struct
        {
            T i;
            if (!Enum.TryParse(s, true, out i))
            {
                return SetInValid(msg);
            }
            return SetValid(i, propertyName);
        }

        private static Regex regChar = new Regex(@"^[A-Za-z]+$");
        public ValidateHelp IsChar(string s, string msg = null, string propertyName = null)
        {
            if (Interrupt(s, propertyName)) return this;
            if (!regChar.IsMatch(s))
            {
                return SetInValid(msg);
            }
            return SetValid(s, propertyName);
        }

        private static Regex regNumber = new Regex(@"^\d+$");
        public ValidateHelp IsNumber(string s, string msg = "")
        {
            if (Interrupt(s, string.Empty)) return this;
            if (!regNumber.IsMatch(s))
            {
                return SetInValid(msg);
            }
            return SetValid(s, string.Empty);
        }

        private static Regex regChinese = new Regex(@"^[\u4e00-\u9fa5]+$");
        public ValidateHelp IsChinese(string s, string msg = null, string propertyName = null)
        {
            if (Interrupt(s, propertyName)) return this;
            if (!regChinese.IsMatch(s))
            {
                return SetInValid(msg);
            }
            return SetValid(s, propertyName);
        }

        private static Regex regEmail = new Regex(@"^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$");
        public ValidateHelp IsEmail(string s, string msg = null, string propertyName = null)
        {
            if (Interrupt(s, propertyName)) return this;
            if (!regEmail.IsMatch(s))
            {
                return SetInValid(msg);
            }
            return SetValid(s, propertyName);
        }

        private static Regex regIP = new Regex(@"^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)$");
        public ValidateHelp IsIP(string s, string msg = null, string propertyName = null)
        {
            if (Interrupt(s, propertyName)) return this;
            if (!regIP.IsMatch(s))
            {
                return SetInValid(msg);
            }
            return SetValid(s, propertyName);
        }

        private static Regex regUrl = new Regex(@"^([a-zA-z]+://)?(\w+(-\w+)*)(\.(\w+(-\w+)*))*(\?\S*)?$");
        public ValidateHelp IsUrl(string s, string msg = null, string propertyName = null)
        {
            if (Interrupt(s, propertyName)) return this;
            if (!regUrl.IsMatch(s))
            {
                return SetInValid(msg);
            }
            return SetValid(s, propertyName);
        }

        public ValidateHelp IsRegex(string s, string msg, string pattern)
        {
            if (Interrupt(s, string.Empty)) return this;
            if (!Regex.IsMatch(s, pattern))
            {
                return SetInValid(msg);
            }
            return SetValid(s, string.Empty);
        }
        #endregion
    }

}
ValidateHelp.cs

结合Web应用

  楼主做的项目一般是Web类型项目,有时候可能会觉得上面的调用方式有点烦躁。比如说:

IsNullOrEmpty(form["password"], "密码不能为空", "password")

  这里写了2个password,第一个是从form中取值,第二个是想保存的Key或PropertyName。

  在Web中,请求的Url参数在Request.QueryString中,Post的参数在Request.Form中,它们的类型都是NameValueCollection。那么把他们当初数据源,或许会更方便吧。

  源码如下:

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;

namespace MacRead
{
    public class RequestValidateHelp
    {
        #region 属性
        //数据容器字典
        public Dictionary<string, object> Data { get; private set; }
        //数据容器实体
        public Object Entity { get; private set; }
        //错误信息
        public string ErrorMsg { get; private set; }
        // true:验证通过 false:验证未通过
        public bool IsValid { get; private set; }
        #endregion

        #region 字段

        //数据容器实体类型
        private Type EntityType;
        //必需的字段信息
        private string[] RequiredProperty;
        //数据源
        private NameValueCollection Container;
        #endregion

        #region 静态方法 得到对象实例
        public static RequestValidateHelp Begin()
        {
            return new RequestValidateHelp();
        }

        public static RequestValidateHelp BeginDic()
        {
            var v = new RequestValidateHelp();
            v.Data = new Dictionary<string, object>();
            return v;
        }

        public static RequestValidateHelp BeginEntity<T>()
        {
            var v = new RequestValidateHelp();
            v.EntityType = typeof(T);
            v.Entity = Activator.CreateInstance(v.EntityType);
            return v;
        }
        #endregion

        #region 辅助方法
        public object this[string key]
        {
            get
            {
                object o;
                if (Data.TryGetValue(key, out o)) return o;
                return null;
            }
        }

        /// <summary>
        /// 指明必需的属性 不允许为null
        /// </summary>
        /// <param name="requiredProperty"></param>
        /// <returns></returns>
        public RequestValidateHelp Require(params string[] requiredProperty)
        {
            if (this.RequiredProperty != null) throw new Exception("Require can‘t be called multiple times ");
            this.RequiredProperty = requiredProperty;
            return this;
        }
        #endregion

        #region 私有方法
        private RequestValidateHelp()
        {
            IsValid = true;//默认验证通过
            var Request = System.Web.HttpContext.Current.Request;
            //构造请求上下文(无论是get还是post都允许携带Url参数,post时允许携带body
            Container = new NameValueCollection(Request.QueryString);
            if (Request.HttpMethod == "POST")
            {
                Container.Add(Request.Form);
            }
        }

        /// <summary>
        /// 数据验证不通过
        /// </summary>
        /// <param name="msg">错误信息提示</param>
        /// <returns></returns>
        private RequestValidateHelp SetInValid(string msg)
        {
            IsValid = false;
            ErrorMsg = msg;
            return this;
        }

        /// <summary>
        /// 数据验证通过
        /// </summary>
        /// <param name="value">数据</param>
        /// <param name="keyName">属性名</param>
        /// <returns></returns>
        private RequestValidateHelp SetValid(object value, string keyName)
        {
            if (!IsValid) IsValid = true;
            if (keyName != null && keyName.Length > 0)
            {
                if (Data != null)
                {
                    Data[keyName] = value;
                }
                else if (Entity != null)
                {
                    var p = Entity.GetType().GetProperty(keyName);
                    //为对象属性赋值
                    p.SetValue(Entity, value, null);
                }
            }
            return this;
        }

        /// <summary>
        /// 验证检查 返回True 终止验证,false 继续验证
        /// </summary>
        /// <param name="s">输入值</param>
        /// <param name="propertyName">属性值</param>
        /// <returns></returns>
        private bool Interrupt(string s, string propertyName)
        {
            //验证无效,直接返回
            if (!IsValid) return true;
            /*继续验证条件 
             * s非空
             * 未指明属性名
             * 未定义必需的属性
             */
            if (s != null ||
                propertyName == null ||
                propertyName.Length == 0 ||
                RequiredProperty == null ||
                RequiredProperty.Length == 0) return false;
            //必需属性数组中包含属性时,
            var i = Array.IndexOf(RequiredProperty, propertyName);
            if (i > -1) // 属性还被指定必需,则直接判定为无效,终止验证
            {
                IsValid = false;
                ErrorMsg = propertyName + " is null";
            }
            // 属性为null时 中断验证
            return true;
        }
        #endregion

        #region 验证方法
        public RequestValidateHelp IsNullOrEmpty(string keyName, string msg = null, string propertyName = null)
        {
            var s = Container[keyName];
            if (Interrupt(s, keyName)) return this;
            if (s == null || s.Length == 0)
            {
                return SetInValid(msg);
            }
            return SetValid(s, propertyName ?? keyName);
        }

        public RequestValidateHelp IsInt(string keyName, string msg = null, string propertyName = null)
        {
            var s = Container[keyName];
            if (Interrupt(s, keyName)) return this;
            int i;
            if (!int.TryParse(s, out i))
            {
                return SetInValid(msg);
            }
            return SetValid(s, propertyName ?? keyName);
        }

        public RequestValidateHelp IsDouble(string keyName, string msg = null, string propertyName = null)
        {
            var s = Container[keyName];
            if (Interrupt(s, keyName)) return this;
            double i;
            if (!double.TryParse(s, out i))
            {
                return SetInValid(msg);
            }
            return SetValid(s, propertyName ?? keyName);
        }

        public RequestValidateHelp IsLong(string keyName, string msg = null, string propertyName = null)
        {
            var s = Container[keyName];
            if (Interrupt(s, keyName)) return this;
            long i;
            if (!long.TryParse(s, out i))
            {
                return SetInValid(msg);
            }
            return SetValid(s, propertyName ?? keyName);
        }

        public RequestValidateHelp IsFloat(string keyName, string msg = null, string propertyName = null)
        {
            var s = Container[keyName];
            if (Interrupt(s, keyName)) return this;
            float i;
            if (!float.TryParse(s, out i))
            {
                return SetInValid(msg);
            }
            return SetValid(s, propertyName ?? keyName);
        }

        public RequestValidateHelp IsBool(string keyName, string msg = null, string propertyName = null)
        {
            var s = Container[keyName];
            if (Interrupt(s, keyName)) return this;
            bool i;
            if (!bool.TryParse(s, out i))
            {
                return SetInValid(msg);
            }
            return SetValid(s, propertyName ?? keyName);
        }

        public RequestValidateHelp IsDateTime(string keyName, string msg = null, string propertyName = null)
        {
            var s = Container[keyName];
            if (Interrupt(s, keyName)) return this;
            DateTime i;
            if (!DateTime.TryParse(s, out i))
            {
                return SetInValid(msg);
            }
            return SetValid(s, propertyName ?? keyName);
        }

        public RequestValidateHelp IsEnum<T>(string keyName, string msg = null, string propertyName = null) where T : struct
        {
            var s = Container[keyName];
            T i;
            if (!Enum.TryParse(s, true, out i))
            {
                return SetInValid(msg);
            }
            return SetValid(s, propertyName ?? keyName);
        }

        private static Regex regChar = new Regex(@"^[A-Za-z]+$");
        public RequestValidateHelp IsChar(string keyName, string msg = null, string propertyName = null)
        {
            var s = Container[keyName];
            if (Interrupt(s, keyName)) return this;
            if (!regChar.IsMatch(s))
            {
                return SetInValid(msg);
            }
            return SetValid(s, propertyName ?? keyName);
        }

        private static Regex regNumber = new Regex(@"^\d+$");
        public RequestValidateHelp IsNumber(string keyName, string msg = null, string propertyName = null)
        {
            var s = Container[keyName];
            if (Interrupt(s, string.Empty)) return this;
            if (!regNumber.IsMatch(s))
            {
                return SetInValid(msg);
            }
            return SetValid(s, string.Empty);
        }

        private static Regex regChinese = new Regex(@"^[\u4e00-\u9fa5]+$");
        public RequestValidateHelp IsChinese(string keyName, string msg = null, string propertyName = null)
        {
            var s = Container[keyName];
            if (Interrupt(s, keyName)) return this;
            if (!regChinese.IsMatch(s))
            {
                return SetInValid(msg);
            }
            return SetValid(s, propertyName ?? keyName);
        }

        private static Regex regEmail = new Regex(@"^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$");
        public RequestValidateHelp IsEmail(string keyName, string msg = null, string propertyName = null)
        {
            var s = Container[keyName];
            if (Interrupt(s, keyName)) return this;
            if (!regEmail.IsMatch(s))
            {
                return SetInValid(msg);
            }
            return SetValid(s, propertyName ?? keyName);
        }

        private static Regex regIP = new Regex(@"^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)$");
        public RequestValidateHelp IsIP(string keyName, string msg = null, string propertyName = null)
        {
            var s = Container[keyName];
            if (Interrupt(s, keyName)) return this;
            if (!regIP.IsMatch(s))
            {
                return SetInValid(msg);
            }
            return SetValid(s, propertyName ?? keyName);
        }

        private static Regex regUrl = new Regex(@"^([a-zA-z]+://)?(\w+(-\w+)*)(\.(\w+(-\w+)*))*(\?\S*)?$");
        public RequestValidateHelp IsUrl(string keyName, string msg = null, string propertyName = null)
        {
            var s = Container[keyName];
            if (Interrupt(s, keyName)) return this;
            if (!regUrl.IsMatch(s))
            {
                return SetInValid(msg);
            }
            return SetValid(s, propertyName ?? keyName);
        }

        public RequestValidateHelp IsRegex(string keyName, string pattern, string msg = null, string propertyName = null)
        {
            var s = Container[keyName];
            if (Interrupt(s, string.Empty)) return this;
            if (!Regex.IsMatch(s, pattern))
            {
                return SetInValid(msg);
            }
            return SetValid(s, string.Empty);
        }
        #endregion
    }
}
RequestValidateHelp.cs

  在验证方法的参数中,依旧保留了2个Key一样的参数,看定义:

        public RequestValidateHelp IsNullOrEmpty(string keyName, string msg = null, string propertyName = null)

  propertyName 作为在保存验证成功后的值的键名或者属性名,如果为null,则默认使用keyName,即取值的Key。

  为什么需要这样多次一举,看示例吧。

    $("#btnLogin").on('click',function(){
        var viewModel = {
            uid: $("#txtUserName").val(),
            pwd: $("#txtPassword").val()
        };
        if (viewModel.uid == '') {
            alert('用户名不能为空');
            return;
        }
        if (viewModel.pwd == '') {
            alert('密码不能为空');
            return;
        }
        $.post('/Account/Login?tt=1', viewModel, function (response) {
            if (!response.Result) {
                alert(response.Message);
                return;
            }
            window.location.href = 'admin/index.html';
        },'json');
    });

  js提交到后端的json数据是uid和pwd,但实际上后台定义的实体类似 UserName,Password等属性,呃,可能是不想过多的让别人了解后台的类定义吧。。。

写在最后

  由于是近2天的思考,楼主希望能同大家交流下,时间有点仓促,代码中难免有疏漏或者错误的地方。

  重在思想,代码的表现形式会有很多,更优秀的代码就是下一行。

  下班了。

转载于:https://www.cnblogs.com/codealone/p/3687245.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值