Unity注册登录Demo的实现-03(全部完整代码)(客户端、服务端、数据库)

21 篇文章 0 订阅
7 篇文章 0 订阅

流程

客户端

服务端

---------------------------------------------------------------------------------------------------------------------------------

客户端

PhotonEngine.cs:差不多算是主类吧,将其构造成单例,到时候方便调用

里面有处理服务端返回的响应的方法,还有接受客户端发来的事件的方法等等。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using ExitGames.Client.Photon;
using Common;

public class PhotonEngine : MonoBehaviour,IPhotonPeerListener {
    public static PhotonEngine Instance;

    //用字典来缓存每次的请求和码
    //这里就能体现出来我们Request抽象类的好处了,不然每次这边都是不同的类型,使用抽象类就统一了
    private Dictionary<OperationCode, Request> RequestDic = new Dictionary<OperationCode, Request>();

    public static PhotonPeer peer;
    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            //当前Gameobject不会随场景的销毁而销毁
            DontDestroyOnLoad(this.gameObject);
        }
    }

    void Start () {
        peer = new PhotonPeer(this,ConnectionProtocol.Udp);
        peer.Connect("127.0.0.1:5055", "MyGame6");//通过ip连接到到对应的我们config中配置的Allpication
	}
	
	void Update () {
        peer.Service();//开启服务
	}

    private void OnDestroy()//销毁的时候
    {
        if(peer!=null&&peer.PeerState == PeerStateValue.Connected)//如果存在且保持连接状态
        {
            peer.Disconnect();//就断开连接
        }
    }

    public void DebugReturn(DebugLevel level, string message)
    {
    }

    public void OnEvent(EventData eventData)
    {
        switch (eventData.Code)
        {
            case 1:
                //解析数据
                var data = eventData.Parameters;
                object intValue, StringValue;
                data.TryGetValue(1, out intValue);
                data.TryGetValue(2, out StringValue);
                Debug.Log("收到服务器的响应Event推送,OpCode:1" + intValue.ToString() + ":" + StringValue.ToString());
                break;
            default:
                break;
        }
    }

    public void OnOperationResponse(OperationResponse operationResponse)
    {
        Request request = null;
        //此处就是工具类
        DicTool.GetValue(RequestDic, (OperationCode)operationResponse.OperationCode)
            .OnOprationRespionse(operationResponse);

        //下面注释的代码可以用上方工具类代替
        //bool b =RequestDic.TryGetValue((OperationCode)operationResponse.OperationCode, out request);
        //if (b)
        //{
        //    request.OnOprationRespionse(operationResponse);
        //}
        //else
        //{
        //    Debug.LogError("未找到对应code的请求request");
        //}

        return;
        //switch (operationResponse.OperationCode)
        //{
        //    case 1:
        //        Debug.Log("收到服务器的响应,OpCode:1");

        //        //解析数据
        //        var data = operationResponse.Parameters;
        //        object intValue;
        //        data.TryGetValue(1, out intValue);
        //        object StringValue;
        //        data.TryGetValue(2, out StringValue);
        //        Debug.Log("收到客户端的请求,OpCode:1" + intValue.ToString() + ":" + StringValue.ToString());
        //        break;
        //            default:
        //                break;
        //        }
        }

    public void OnStatusChanged(StatusCode statusCode)
    {
        Debug.LogError(statusCode);
    }

    //private Dictionary<OperationCode, Request> RequestDic = new Dictionary<OperationCode, Request>();

    public void AddRequest(Request r)
    {
        RequestDic.Add(r.OpCode, r);
    }
    public void RemoveRequest(Request r)
    {
        RequestDic.Remove(r.OpCode);
    }
}

Request.cs:抽象了,生命code和request以及对服务器返回的响应的响应response

using Common;
using ExitGames.Client.Photon;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public abstract class Request: MonoBehaviour
{
    public OperationCode OpCode;

    //抽象方法就是只声明,具体的实现有子类完成
    public abstract void DefaultRequest();
    public abstract void OnOprationRespionse(OperationResponse operationResponse);

    public void Start()
    {
        PhotonEngine.Instance.AddRequest(this);
    }

    public void OnDestroy()
    {
        PhotonEngine.Instance.RemoveRequest(this);
    }


}

(登录)LoginRequest.cs:实现了抽象类中的方法

using Common;
using ExitGames.Client.Photon;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LoginRequest : Request
{
    //在unity面板隐藏
    [HideInInspector]
    public string UserName;
    [HideInInspector]
    public string Password;
    public override void DefaultRequest()
    {
        //构造参数
        var data = new Dictionary<byte, object>();
        //构造参数
        data.Add((byte)ParaCode.UserName,UserName);
        data.Add((byte)ParaCode.Password, Password);
        //发送
        PhotonEngine.peer.OpCustom((byte)OpCode, data, true);
    }

    public override void OnOprationRespionse(OperationResponse operationResponse)
    {
        Debug.Log(operationResponse.ReturnCode);
    }
}

(登录)LoginPanel.cs:用于获得输入的账号密码,以及点击的时候调用request

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class LoginPanel : MonoBehaviour {
   public GameObject SignPanel;
    void Start() {
     //   SignPanel = GameObject.Find("SignInPanel");

    }

    void Update() {

    }
    public void OnLoginBtn()
    {
        print("OnLoginBtn()");

        var  Request = GetComponent<LoginRequest>();
        Request.UserName = transform.Find("NameField").GetComponent<InputField>().text;
        Request.Password = transform.Find("PWField").GetComponent<InputField>().text;
        Request.DefaultRequest();
    }
    public void OnSign()
    {
        SignPanel.SetActive(true);
        gameObject.SetActive(false);
    }
}

(注册)LoginRequest.cs:实现抽象类中的方法

using Common;
using ExitGames.Client.Photon;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LoginRequest : Request
{
    //在unity面板隐藏
    [HideInInspector]
    public string UserName;
    [HideInInspector]
    public string Password;
    public override void DefaultRequest()
    {
        //构造参数
        var data = new Dictionary<byte, object>();
        //构造参数
        data.Add((byte)ParaCode.UserName,UserName);
        data.Add((byte)ParaCode.Password, Password);
        //发送
        PhotonEngine.peer.OpCustom((byte)OpCode, data, true);
    }

    public override void OnOprationRespionse(OperationResponse operationResponse)
    {
        Debug.Log(operationResponse.ReturnCode);
    }
}

(注册)LoginPanel.cs:用于获得输入的账号密码,以及点击的时候调用request

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class LoginPanel : MonoBehaviour {
   public GameObject SignPanel;
    void Start() {
     //   SignPanel = GameObject.Find("SignInPanel");

    }

    void Update() {

    }
    public void OnLoginBtn()
    {
        print("OnLoginBtn()");

        var  Request = GetComponent<LoginRequest>();
        Request.UserName = transform.Find("NameField").GetComponent<InputField>().text;
        Request.Password = transform.Find("PWField").GetComponent<InputField>().text;
        Request.DefaultRequest();
    }
    public void OnSign()
    {
        SignPanel.SetActive(true);
        gameObject.SetActive(false);
    }
}

---------------------------------------------------------------------------------------------------------------------------------

服务端

其中Nhibernate就不发了,之前的文章里发过了。(有用的)

然后这里面net连接mysql那个文件夹是之前测试用的,这里也不发了。(没用)

就发剩余的了(有用的)

Class1.cs:枚举,我认为这是协议

namespace Common
{
    public enum OperationCode : byte
    {
        Login,
        SignIn
    }

    public enum ParaCode : byte
    {
        UserName,
        Password
    }

    public enum ReturnCode : short
    {
        Success,
        Failed
    }

}

DicTools.cs:字典工具类,照着写,拿着用就完事了

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

namespace Common
{
    public class DicTool
    {
        public static T2 GetValue<T1,T2>(Dictionary<T1,T2> dic, T1 key)
        {
            T2 value;
            bool isSuccess = dic.TryGetValue(key, out value);
            if (isSuccess)
            {
                return value;
            }
            else
            {
                return default(T2);
            }
        }

    }
}

BaseHandler.cs:和客户端的Request差不多,都是抽象类。定义code以及抽象方法

using Common;
using Photon.SocketServer;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RRGameServer.Handler
{
    public abstract class BaseHandler
    {
        public OperationCode OpCode;

        public abstract void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters, ClientPeer peer);

    }
}

LoginHandler.cs:拿到登陆的请求,实现抽象类的方法,返回响应

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Common;
using Photon.SocketServer;
using RRGameServer.Manager;

namespace RRGameServer.Handler
{
    class LoginHandler : BaseHandler
    {
        public LoginHandler()
        {
            OpCode = Common.OperationCode.Login;
        }

        //服务端收到客户端的request,并且对其做出响应返回给客户端
        public override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters, ClientPeer peer)
        {
            string username = DicTool.GetValue<byte, object>(operationRequest.Parameters, (byte)ParaCode.UserName) as string;
            string password = DicTool.GetValue<byte, object>(operationRequest.Parameters, (byte)ParaCode.Password) as string;

           bool b= UserManager.Instance.VerifyUser(username, password);
            OperationResponse response = new OperationResponse(operationRequest.OperationCode);
            if (b)
            {
                response.ReturnCode = (short)Common.ReturnCode.Success;
            }
            else
            {
                response.ReturnCode = (short)Common.ReturnCode.Failed;
            }
            peer.SendOperationResponse(response, sendParameters);
        }
    }
}

SignHandler.cs:拿到注册的请求,实现抽象类的方法,返回响应

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Common;
using Photon.SocketServer;
using RRGameServer.Manager;
using RRGameServer.Model;

namespace RRGameServer.Handler
{
    class SignHandler:BaseHandler
    {
        public SignHandler()
        {
            OpCode = Common.OperationCode.SignIn;
        }

        public override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters, ClientPeer peer)
        {
            string username = DicTool.GetValue<byte, object>(operationRequest.Parameters, (byte)ParaCode.UserName) as string;
            string password = DicTool.GetValue<byte, object>(operationRequest.Parameters, (byte)ParaCode.Password) as string;

            var user = UserManager.Instance.GetUserByName(username);
            OperationResponse response = new OperationResponse(operationRequest.OperationCode);
            if (user==null)
            {
                user = new User();
                user.Username = username;
                user.Password = password;
                UserManager.Instance.Add(user);
                response.ReturnCode = (short)Common.ReturnCode.Success;
            }
            else
            {
                response.ReturnCode = (short)Common.ReturnCode.Failed;
            }
            peer.SendOperationResponse(response, sendParameters);
        }
    }
}

UserManager.cs:可以对数据库进行操作,,增删改查以及比对校验啥的

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NHibernate;
using NHibernate.Criterion;
using RRGameServer.Model;

namespace RRGameServer.Manager
{
    class UserManager
    {
        public static UserManager Instance = new UserManager();
        public void Add(User user)
        {
            //using大括号之间的代码执行完毕之后,()里面的内存就会被回收
            using (ISession session= NHibernateHelper.OpenSession())
            {
                using (ITransaction transaction = session.BeginTransaction())
                {
                    session.Save(user);
                    transaction.Commit();
                }
            }
        }

        public void Update(User user)
        {
            //using大括号之间的代码执行完毕之后,()里面的内存就会被回收
            using (ISession session = NHibernateHelper.OpenSession())
            {
                using (ITransaction transaction = session.BeginTransaction())
                {
                    session.Update(user);
                    transaction.Commit();
                }
            }
        }

        //做用户名和密码的校验
        internal bool VerifyUser(string username, string password)
        {
            using (ISession session = NHibernateHelper.OpenSession())
            {
                User user= session
                    .CreateCriteria(typeof(User))
                    .Add(Restrictions.Eq("Username", username))
                    .Add(Restrictions.Eq("Password", password))
                    .UniqueResult<User>();
                if (user == null)
                    return false;
                return true;
            }
        }

        public void Remove(User user)
        {
            //using大括号之间的代码执行完毕之后,()里面的内存就会被回收
            using (ISession session = NHibernateHelper.OpenSession())
            {
                using (ITransaction transaction = session.BeginTransaction())
                {
                    session.Delete(user);
                    transaction.Commit();
                }
            }
        }

        public User GetUserById(int id)
        {
            //using大括号之间的代码执行完毕之后,()里面的内存就会被回收
            using (ISession session = NHibernateHelper.OpenSession())
            {
                using (ITransaction transaction = session.BeginTransaction())
                {
                    var u = session.Get<User>(id);
                    transaction.Commit();
                    return u;
                }
            }
        }

        public User GetUserByName(string name)
        {
            //using大括号之间的代码执行完毕之后,()里面的内存就会被回收
            using (ISession session = NHibernateHelper.OpenSession())
            {
                return session.CreateCriteria(typeof(User)).Add(Restrictions.Eq("Username", name)).UniqueResult<User>();
            }
        }

        public IList<User> GetAll()
        {
            //using大括号之间的代码执行完毕之后,()里面的内存就会被回收
            using (ISession session = NHibernateHelper.OpenSession())
            {
                return session.CreateCriteria(typeof(User)).List<User>();
            }
        }

    }
}

User.hbm.xml:配置表,配置与数据库交互

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
                   assembly="RRGameServer"
                   namespace="RRGameServer.Model">

  <!-- more mapping info here -->

  <class name="User" table="users">
    <id name="Id" column="id" type="Int32">
      <generator class="native"></generator>
    </id>
    <property name="Username" column="username" type="string"></property>
    <property name="Password" column="password" type="string"></property>
    <property name="Registerdate" column="registerdate" type="Date"></property>
  </class>
  
</hibernate-mapping>

users.cs:用户类,跟NHibernate一起用的

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RRGameServer.Model
{
    class User
    {
        public virtual int Id { get; set; }
        public virtual string Username { get; set; }
        public virtual string Password { get; set; }
        public virtual DateTime Registerdate { get; set; }

    }
}

ClientPeer.cs:处理客户端请求等功能

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Common;
using Photon.SocketServer;
using PhotonHostRuntimeInterfaces;
using RRGameServer.Handler;

namespace RRGameServer
{
    public class ClientPeer : Photon.SocketServer.ClientPeer
    {
        public ClientPeer(InitRequest initRequest) : base(initRequest)
        {
        }

        //当每个客户端断开时
        protected override void OnDisconnect(DisconnectReason reasonCode, string reasonDetail)
        {
            
        }

        //当客户端发起请求的时候
        protected override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters)
        {
            BaseHandler handler = DicTool.GetValue(MyGameServer.Instance.HandlerDic, (OperationCode)operationRequest.OperationCode);
            if (handler != null)
            {
                handler.OnOperationRequest(operationRequest, sendParameters,this);
            }
            else
            {
                MyGameServer.log.Info("找不到操作码:" + (OperationCode)operationRequest.OperationCode);
            }

            return;
            //switch (operationRequest.OperationCode)
            //{
            //    case 1:
            //        //解析数据
            //        var data = operationRequest.Parameters;
            //        object intValue;
            //        data.TryGetValue(1, out intValue);
            //        object StringValue;
            //        data.TryGetValue(2, out StringValue);
            //        //输出参数
            //        MyGameServer.log.Info("收到客户端的请求,opcode:1"+intValue.ToString()+":"+StringValue.ToString());

            //        //返回响应
            //        OperationResponse opResponse = new OperationResponse(operationRequest.OperationCode);

            //        //构造参数
            //        var data2 = new Dictionary<byte, object>();
            //        data2.Add(1, 100);
            //        data2.Add(2, "这是服务端做的响应");
            //        opResponse.SetParameters(data2);
            //        //返回code,为发送过来的code,返回的参数,为发送过来的参数
            //        SendOperationResponse(opResponse, sendParameters);

            //        //推送一个Event
            //        EventData ed = new EventData(1);
            //        ed.Parameters = data2;
            //        SendEvent(ed, new SendParameters());
            //        break;
            //    default:
            //        break;
            //}
        }
    }
}

hibernate.cfg.xml:配置用户连接数据库啥的

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
  <session-factory>
    <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
    <property name="dialect">NHibernate.Dialect.MsSqlCeDialect</property>
    <property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
    <property name="connection.connection_string">server=localhost;port=3306;database=gamedb;user=root;password=root</property>

    <property name="show_sql">true</property>
  </session-factory>
</hibernate-configuration>

MyGameServer.cs:主类,用于初始化字典,以及服务器的响应初始化,以及日志等操作。

(重要!)(●'◡'●)

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Common;
using ExitGames.Logging;
using ExitGames.Logging.Log4Net;
using log4net.Config;
using Photon.SocketServer;
using RRGameServer.Handler;
using RRGameServer.Manager;

namespace RRGameServer
{
    //所有的Server,都要继承ApplicationBase,然后实现ApplicationBase的三个方法
    public class MyGameServer : ApplicationBase
    {
        public static MyGameServer Instance;
        public static readonly ILogger log = LogManager.GetCurrentClassLogger();
        //当有一个客户端连接上以后,就会执行此方法
        protected override PeerBase CreatePeer(InitRequest initRequest)
        {
            log.Info("一个客户端连接");
            return new ClientPeer(initRequest);
        }

        //服务器初始化函数
        protected override void Setup()
        {
            Instance = this;
            log4net.GlobalContext.Properties["Photon:ApplicationLogPath"] = Path.Combine(
                Path.Combine(this.ApplicationRootPath, "bin_Win64"), "log");
            FileInfo configFileInfo = new FileInfo(Path.Combine(this.BinaryPath, "log4net.config"));

            if (configFileInfo.Exists)
            {
                LogManager.SetLoggerFactory(Log4NetLoggerFactory.Instance);//photon知道日志输出
                XmlConfigurator.ConfigureAndWatch(configFileInfo);//读取配置
            }
            log.Info("服务器启动啦");

            //log.Info(UserManager.Instance.GetUserById(7).Username);
            InitHandler();//初始化
        }

        //服务器关闭函数
        protected override void TearDown()
        {
            log.Info("服务器关闭啦");
        }

        public Dictionary<OperationCode, BaseHandler> HandlerDic = new Dictionary<OperationCode, BaseHandler>();

        public void InitHandler()
        {
            //一开始就全部初始化
            LoginHandler LH = new LoginHandler();
            //log.Info("初始化操作码:" + LH.OpCode);
            HandlerDic.Add(LH.OpCode, LH);
            SignHandler handler = new SignHandler();
            HandlerDic.Add(handler.OpCode, handler);
        }

    }
}

NHibernateHelper.cs:会话工厂,用于搞Nhibernate的那玩意,直接用就完事了。

using NHibernate;
using NHibernate.Cfg;
using RRGameServer.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RRGameServer
{
    class NHibernateHelper
    {
        private static ISessionFactory _sessionFactory;
        private static ISessionFactory sessionFactory
        {
            get
            {
                if (_sessionFactory == null)
                {
                    var cfg = new Configuration();
                    cfg.Configure();//解析配置文件
                    cfg.AddAssembly("RRGameServer");
                    _sessionFactory = cfg.BuildSessionFactory();
                }
                return _sessionFactory;
            }
        }
        public static ISession OpenSession()
        {
            return sessionFactory.OpenSession();
        }
    }
}

最后效果也懒得贴图了,就是unity的客户端和服务端以及数据库三者打通了。

客户端注册,服务端接受到注册,判断有没有,与数据库做对比,如果有的话,返回失败。如果没有,添加到数据库,返回给客户端成功。然后客户端在根据返回的码,做相应的操作。

客户端登陆,服务端接收到登录,判断账号密码和数据库里面是否相同,如果相同,返回成功,如果不同,返回失败。然后客户端在根据返回的码,做相应的操作。

基本上就三个函数,一个requst是客户端发送给服务端的请求。然后服务端会有一个response函数,用于接受客户端发过来的请求,并且你在处理这个请求的时候,还可以掉个api给客户端发回去你的处理结果。然后第三个函数是客户端用于接受服务端传回来的响应的函数,可以根据传回来的响应,做一些操作,比如弹出成功登录的页面啥的呀。

(●'◡'●)

( •̀ ω •́ )y

呜呜终于一个人配置、搭建、搞通了客户端和服务端还有数据库,搭建了架构QAQ

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在Unity中连接MySQL数据库实现登录注册功能,需要遵循以下步骤: 1. 安装MySQL Connector/NET:在Unity项目中连接MySQL数据库,需要安装MySQL Connector/NET。可以从MySQL官网下载适合您的操作系统和Unity版本的Connector/NET。 2. 创建数据库和表:在MySQL中创建一个数据库,然后创建一个表,用于存储用户信息,包括用户名和密码。 3. 编写C#代码:在Unity中编写C#代码来连接数据库和执行查询。 下面是一个示例代码,用于实现登录和注册功能: ```csharp using UnityEngine; using System.Collections; using System.Data; using System.Data.SqlClient; public class DBManager : MonoBehaviour { private static string connectionString = "Server=your_server;Database=your_database;Uid=your_username;Pwd=your_password;"; private static SqlConnection connection = new SqlConnection(connectionString); public static bool Login(string username, string password) { connection.Open(); SqlCommand command = new SqlCommand("SELECT * FROM users WHERE username=@username AND password=@password", connection); command.Parameters.AddWithValue("@username", username); command.Parameters.AddWithValue("@password", password); SqlDataReader reader = command.ExecuteReader(); bool success = reader.HasRows; reader.Close(); connection.Close(); return success; } public static bool Register(string username, string password) { connection.Open(); SqlCommand command = new SqlCommand("INSERT INTO users (username, password) VALUES (@username, @password)", connection); command.Parameters.AddWithValue("@username", username); command.Parameters.AddWithValue("@password", password); int rowsAffected = command.ExecuteNonQuery(); connection.Close(); return rowsAffected == 1; } } ``` 这个示例代码包含两个方法:Login和Register。Login方法接收一个用户名和密码并在数据库中查找匹配的记录。如果找到了匹配的记录,则返回true,否则返回false。Register方法接收一个用户名和密码并将其插入到数据库中。如果插入成功,则返回true,否则返回false。 请注意,这只是一个示例代码,并且需要根据您的数据库结构进行修改。另外,为了安全起见,密码应该使用哈希函数进行加密,而不是明文存储。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值