Unity-网络编程 笔记3

客户端界面

客户端实现注册

客户端对注册结果的处理

创建脚本获取UI界面元素

创建 LoginView.cs 获取UI界面中的组件以及添加上事件

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

public class LoginView : MonoBehaviour
{
    public static LoginView Instance;
    [Header("login组件")]
    public InputField l_username;
    public InputField l_password;
    public Button l_loginBtn;
    public Button l_registerBtn;

    [Header("Register组件")]
    public GameObject registerView;
    public InputField r_username;
    public InputField r_password;
    public InputField r_email;
    public Button r_registerBtn;
    public Button r_closeBtn;

    [Header("TipsItem组件")]
    public GameObject _tipsObj;//缓存
    public GameObject tipsItem;

    public GameObject chatObj;
    void Start()
    {
        Instance = this;
        Init();
    }

    private void Init()
    {
        //获取组件
        //登录界面组件获取
        l_username = transform.Find("UsernameInput").GetComponent<InputField>();
        l_password = transform.Find("PasswordInput").GetComponent<InputField>();
        l_loginBtn = transform.Find("LoginBtn").GetComponent<Button>();
        l_registerBtn = transform.Find("RegisterBtn").GetComponent<Button>();

        //注册界面组件获取
        registerView = transform.Find("Register").gameObject;
        r_username = transform.Find("Register/View/UsernameInput").GetComponent<InputField>();
        r_password = transform.Find("Register/View/PasswordInput").GetComponent<InputField>();
        r_email = transform.Find("Register/View/EmailInput").GetComponent<InputField>();
        r_registerBtn = transform.Find("Register/View/RegisterBtn").GetComponent<Button>();
        r_closeBtn = transform.Find("Register/View/CloseBtn").GetComponent<Button>();

        //UI提示
        tipsItem = transform.Find("TipsItem").gameObject;

        //添加事件
        l_loginBtn.onClick.AddListener(LoginBtnClick);
        l_registerBtn.onClick.AddListener(RegisterBtn1Click);
        r_registerBtn.onClick.AddListener(RegisterBtn2Click);
        r_closeBtn.onClick.AddListener(CloseBtnClick);

        
    }

    void LoginBtnClick()
    {
        
    }
    void RegisterBtn1Click()
    {
        
    }
    void RegisterBtn2Click()
    {
        
    }

    void CloseBtnClick()
    {

    }

    void Tips(string str)
    {
        if(_tipsObj != null)
        {
            Destroy(_tipsObj);
        }
        
        //克隆提示物体
        _tipsObj = Instantiate(tipsItem, transform);
        //修改提示显示文本
        Text content = _tipsObj.GetComponentInChildren<Text>();
        content.text = str;
        //显示文本
        _tipsObj.SetActive(true);
        //使物体存在1.5秒后删除
        Destroy(_tipsObj, 1.5f);
    }
}

创建 ChatView.cs 获取UI界面中的组件以及添加上事件

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

public class ChatView : MonoBehaviour
{
    public Button closeBtn;

    
    void Start()
    {
        //获取组件
        closeBtn = transform.Find("Popup_Dark/Menu Icon").GetComponent<Button>();
        //添加事件
        closeBtn.onClick.AddListener(LogoutHandle);
    }

    private void LogoutHandle()
    {
        
    }
}

创建json对象 

创建 Module.cs

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

public partial class Module
{
    //客户端向服务端发送的数据对象
    public class RegisterMsgC2S
    {
        public string username;
        public string email;
        public string password;
    }
    
    //服务端向客户端发送的数据对象
    public class RegisterMsgS2C
    {
        public string username;
        public string email;
        public string password;
        public int result;//0:注册成功 1:账号已被注册
    }
}

修改 MsgManager.cs 将switch中添加结果方法

创建一个Action 用于处理注册返回结果

public Action<Module.RegisterMsgS2C> registerHandle;
switch (id)
{
    case 1001://注册请求结果
        MsgHelper.RegisterMsgHandler(body);
        break;
    case 1002://登录请求结果
        MsgHelper.LoginMsgHandler(body);
        break;
    case 1003://聊天请求结果
        MsgHelper.ChatMsgHandler(body);
        break;
    case 1004://退出登录
        MsgHelper.LogoutMsgHandler(body);
        break;
    default:
        Debug.Log("其他ID,还没有该业务逻辑!!!");
        break;
}

在MsgHelper.cs中现实 MsgManager.cs 中的业务处理方法

在 MsgHelper.cs 中实现方法 

    /// <summary>
    /// 处理注册(结果)业务
    /// </summary>
    /// <param name="data"></param>
    public static void RegisterMsgHandler(byte[] data)
    {
        //将接收到的数据进行格式转化
        var str =Encoding.UTF8.GetString(data);
        
        //将数据反序列化
        var result = JsonHelper.ToObject<Module.RegisterMsgS2C>(str);
        
        //触发回调函数
        Client.Instance.msgManager.registerHandle?.Invoke(result);
    }
    /// <summary>
    /// 处理登录(结果)业务
    /// </summary>
    /// <param name="data"></param>
    public static void LoginMsgHandler(byte[] data)
    {
        
    }
    /// <summary>
    /// 处理聊天(转发)业务
    /// </summary>
    /// <param name="data"></param>
    public static void ChatMsgHandler(byte[] data)
    {
        
    }

    /// <summary>
    /// 处理退出登录业务
    /// </summary>
    /// <param name="data"></param>
    public static void LogoutMsgHandler(byte[] data)
    {
          
    }
}

注册 Action

修改 LoginView.cs,在Init 方法中对Action 进行注册

private void Init()
{
    //获取组件
    l_username = transform.Find("UsernameInput").GetComponent<InputField>();
    l_password = transform.Find("PasswordInput").GetComponent<InputField>();
    l_loginBtn = transform.Find("LoginBtn").GetComponent<Button>();
    l_registerBtn = transform.Find("RegisterBtn").GetComponent<Button>();

    registerView = transform.Find("Register").gameObject;
    r_username = transform.Find("Register/View/UsernameInput").GetComponent<InputField>();
    r_password = transform.Find("Register/View/PasswordInput").GetComponent<InputField>();
    r_email = transform.Find("Register/View/EmailInput").GetComponent<InputField>();
    r_registerBtn = transform.Find("Register/View/RegisterBtn").GetComponent<Button>();
    r_closeBtn = transform.Find("Register/View/CloseBtn").GetComponent<Button>();

    tipsItem = transform.Find("TipsItem").gameObject;

    //添加事件
    l_loginBtn.onClick.AddListener(LoginBtnClick);
    l_registerBtn.onClick.AddListener(RegisterBtn1Click);
    r_registerBtn.onClick.AddListener(RegisterBtn2Click);
    r_closeBtn.onClick.AddListener(CloseBtnClick);

    //处理登录注册结果
    Client.Instance.msgManager.registerHandle += RegisterHandle;
}
private void RegisterHandle(Module.RegisterMsgS2C c)
{
    if (c.result == 0)
    {
        Tips("注册成功");
        Debug.Log("注册成功");
        registerView.SetActive(false);
        l_username.text = c.username;
        l_password.text = c.password;
    }
    else
    {
        Tips("帐号已存在");
    }
}

客户端对注册消息进行发送

修改LoginView.cs,完成两个按钮的事件 

/// <summary>
/// 打开注册界面
/// </summary>
void RegisterBtn1Click()
{
    registerView.SetActive(true);
}
/// <summary>
/// 发送注册消息
/// </summary>
void RegisterBtn2Click()
{
    //判断输入
    if (string.IsNullOrEmpty(r_username.text))
    {
        Tips("请输入账号!!!");
        return;
    }
    if (string.IsNullOrEmpty(r_email.text))
    {
        Tips("请输入邮箱!!!");
        return;
    }
    if (string.IsNullOrEmpty(r_password.text))
    {
        Tips("请输入密码!!!");
        return;
    }

    //获取输入信息
    string username = r_username.text;
    string password = r_password.text;
    string email = r_email.text;

    //发送注册消息
    MsgHelper.SendRegisterMsg(username, email, password);
}

/// <summary>
/// 关闭注册界面
/// </summary>
void CloseBtnClick()
{
    registerView.SetActive(false);
}

在MsgHelper.cs中实现注册消息的发送 

/// <summary>
/// 封装消息进行发送
/// </summary>
/// <param name="id">消息ID</param>
/// <param name="json">消息</param>
private static void SendToService(int id, string json)
{
    //将消息数据封装打包成:包体大小+包体ID+包体
    byte[] send_buffer = MsgHelper.MsgEncoding(id, json);
    //发送消息
    Client.Instance.Send(send_buffer);
}

/// <summary>
/// 发送注册消息给服务器
/// </summary>
/// <param name="username"></param>
/// <param name="email"></param>
/// <param name="pwd"></param>
public static void SendRegisterMsg(string username, string email, string pwd)
{
    //实例化对象
    Module.RegisterMsgC2S msg = new Module.RegisterMsgC2S();
    msg.username = username;
    msg.email = email;
    msg.password = pwd;

    //将对象序列化
    var str = JsonHelper.ToJson(msg);
    Debug.Log(str);

    //发送消息
    SendToService(1001, str);
}

服务端对注册结果的处理

创建账号数据库(简单起见这里采用类的方式将账号缓存)

创建三个脚本

        OnlinePlayer.cs:记录当前是否登录

        PlayerData.cs:账号数据

        PlayerInfo.cs:账号信息

using Service.Net;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace Service.Player
{
    internal class OnlinePlayer
    {
        //当前是否在线
        public bool isOnline;
        //当前账户名
        public string username;
    }
}

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

namespace Service.Player
{
    internal class PlayerData
    {
        //创建单例
        public static PlayerData Instance;
        
        //初始化
        public PlayerData() 
        {
            Instance = this;

            players = new List<PlayerInfo>();
            
            //缓存一个账号
            PlayerInfo player = new PlayerInfo();
            
            player.username = "asd";
            player.email = "123@qq.com";
            player.password = "123";

            players.Add(player);
        }

        public List<PlayerInfo> players;
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Service.Player
{
    internal class PlayerInfo
    {
        public string username;
        public string email;
        public string password;
    }
}

创建json对象

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

namespace Service.Modules
{
    internal class Module
    {
        public class RegisterMsgC2S
        {
            public string username;
            public string email;
            public string password;
        }
        public class RegisterMsgS2C
        {
            public string username;
            public string email;
            public string password;
            public int result;//0:注册成功 1:账号已被注册
        }
    }
}

修改 MsgManager.cs 将switch中添加结果方法 

switch (id)
{
    case 1001://注册请求
        MsgHelper.RegisterMsgHandler(body, RegisterHandler);
        break;
    case 1002://登录业务
        MsgHelper.LoginMsgHandler(body, LoginHandler);
        break;
    case 1003://聊天请求
        MsgHelper.ChatMsgHandler(body);
        break;
    case 1004://退出请求
        MsgHelper.LogoutMsgHandler(body, LogoutHandler);
        break;
    default:
        client.SendToService(10000,"服务端还没有该业务!!!");
        break;
}

在MsgHelper.cs中现实 MsgManager.cs 中的业务处理方法

/// <summary>
/// 处理注册业务
/// </summary>
/// <param name="data"></param>
public static void RegisterMsgHandler(byte[] data, Action<Module.RegisterMsgC2S> action)
{
    //将接收到的数据进行格式转化
    string str = Encoding.UTF8.GetString(data);
    //将数据反序列化
    var msg = JsonHelper.ToObject<Module.RegisterMsgC2S>(str);
    //触发回调函数
    action?.Invoke(msg);
}
/// <summary>
/// 处理登录业务
/// </summary>
/// <param name="data"></param>
public static void LoginMsgHandler(byte[] data,Action<Module.LoginMsgC2S> aciton)
{
    
}
/// <summary>
/// 处理聊天业务
/// </summary>
/// <param name="data"></param>
public static void ChatMsgHandler(byte[] data)
{
    
}
/// <summary>
/// 处理退出业务
/// </summary>
/// <param name="data"></param>
/// <param name="aciton"></param>
public static void LogoutMsgHandler(byte[] data, Action<Module.LogoutMsgC2S> aciton)
{
    
}

在MsgManager.cs中实现回调函数

public void RegisterHandler(Module.RegisterMsgC2S msg)
{
    //查找缓存账号中是否存在
    foreach (var info in PlayerData.Instance.players)
    {
        if (msg.username == info.username)
        {
            //账号已存在,注册失败
            MsgHelper.SendRegisterMsg(client, info, 1);
            return;
        }
    }

    //不存在,开始注册
    PlayerInfo player =new PlayerInfo();
    player.username = msg.username;
    player.password = msg.password;
    player.email = msg.email;

    PlayerData.Instance.players.Add(player);

    //发送注册成功消息
    MsgHelper.SendRegisterMsg(client, player, 0);
}

服务端对注册结果消息的发送

在MsgHelper.cs中实现发送方法 

/// <summary>
/// 发送注册结果消息
/// </summary>
/// <param name="client">客户端</param>
/// <param name="info">注册用户信息</param>
/// <param name="result">注册结果</param>
public static void SendRegisterMsg(Client client, PlayerInfo info ,int result)
{
    //创建json对象
    Module.RegisterMsgS2C registerObj = new Module.RegisterMsgS2C();

    registerObj.username = info.username;
    registerObj.password = info.password;
    registerObj.email = info.email;
    registerObj.result = result;
    //将json对象序列化
    string json = JsonHelper.ToJson(registerObj);
    //发送消息
    client.SendToService(1001, json);
}

客户端实现登录与退出

客户端对登录的处理

创建json对象 

public partial class Module
{
    public class LoginMsgC2S
    {
        public string username; 
        public string password;
    }
    public class LoginMsgS2C
    {
        public string username;
        public string password;
        public int result;//0:登录成功 1:登录失败
    }


    public class LogoutMsgC2S
    {
        public string username;
        public string password;
    }

    public class LogoutMsgS2C
    {
        public int type;//0:自主下线 1:被人挤掉线
    }
    
}

在MsgHelper.cs中现实 MsgManager.cs 中的业务处理方法

创建 Action 用于处理返回结果

public Action<Module.RegisterMsgS2C> registerHandle;
public Action<Module.LoginMsgS2C> loginHandle;
public Action<string> logoutHandle;
/// <summary>
/// 处理登录(结果)业务
/// </summary>
/// <param name="data"></param>
public static void LoginMsgHandler(byte[] data)
{
    //数据转化
    var str = Encoding.UTF8.GetString(data);
    var result = JsonHelper.ToObject<Module.LoginMsgS2C>(str);
    //触发回调
    Client.Instance.msgManager.loginHandle?.Invoke(result);
}

/// <summary>
/// 处理退出登录业务
/// </summary>
/// <param name="data"></param>
public static void LogoutMsgHandler(byte[] data)
{
    //数据转化
    var str = Encoding.UTF8.GetString(data);
    var result = JsonHelper.ToObject<Module.LogoutMsgS2C>(str);
    
    //触发回调 1:被他人挤掉线
    if (result.type == 1)
    {
        string tips = "在其他地方登录!!!";
        Client.Instance.msgManager.logoutHandle?.Invoke(tips);
    }
        
}

注册 Action

修改 LoginView.cs,在Init 方法中对Action 进行注册

private void Init()
{
    //获取组件
    l_username = transform.Find("UsernameInput").GetComponent<InputField>();
    l_password = transform.Find("PasswordInput").GetComponent<InputField>();
    l_loginBtn = transform.Find("LoginBtn").GetComponent<Button>();
    l_registerBtn = transform.Find("RegisterBtn").GetComponent<Button>();

    registerView = transform.Find("Register").gameObject;
    r_username = transform.Find("Register/View/UsernameInput").GetComponent<InputField>();
    r_password = transform.Find("Register/View/PasswordInput").GetComponent<InputField>();
    r_email = transform.Find("Register/View/EmailInput").GetComponent<InputField>();
    r_registerBtn = transform.Find("Register/View/RegisterBtn").GetComponent<Button>();
    r_closeBtn = transform.Find("Register/View/CloseBtn").GetComponent<Button>();

    tipsItem = transform.Find("TipsItem").gameObject;

    //添加事件
    l_loginBtn.onClick.AddListener(LoginBtnClick);
    l_registerBtn.onClick.AddListener(RegisterBtn1Click);
    r_registerBtn.onClick.AddListener(RegisterBtn2Click);
    r_closeBtn.onClick.AddListener(CloseBtnClick);

    //处理登录注册结果
    Client.Instance.msgManager.loginHandle += LoginHandle;
    Client.Instance.msgManager.registerHandle += RegisterHandle;
    Client.Instance.msgManager.logoutHandle += LogoutHandle;
}

 实现回调函数

private void LogoutHandle(string tips)
{
    Tips(tips);
    //关闭聊天
    if (chatObj != null)
        Destroy(chatObj);
    //显示登录
    gameObject.SetActive(true);

    l_username.text = "";
    l_password.text = "";
}

private void LoginHandle(Module.LoginMsgS2C c)
{
    if(c.result == 0)
    {
        PlayerData.Instance.LoginMsgS2C = c;
        Tips("登录成功");
        Debug.Log("登录成功");
        this.gameObject.SetActive(false);

        var chatView = Resources.Load<GameObject>("ChatView");
        chatObj = Instantiate(chatView);
        chatObj.AddComponent<ChatView>();
    }
    else
    {
        PlayerData.Instance.LoginMsgS2C = null;
        Tips("帐号或者密码错误");
    }
}

客户端登录与登出消息的发送

实现登录登出按钮

LoginView.cs 登录按钮

void LoginBtnClick()
{
    //判断输入
    if (string.IsNullOrEmpty(l_username.text))
    {
        Tips("请输入账号!!!");
        return;
    }
    if (string.IsNullOrEmpty(l_password.text))
    {
        Tips("请输入密码!!!");
        return;
    }

    //向服务端发送登录
    string username = l_username.text;
    string password = l_password.text;

    MsgHelper.SendLoginMsg(username, password);
}

 ChatView.cs登出按钮

private void LogoutHandle()
{
    //当前是否登录
    if(PlayerData.Instance.LoginMsgS2C != null)
    {    
        //发送登出消息
        MsgHelper.SendLogoutMsg(PlayerData.Instance.LoginMsgS2C.username, PlayerData.Instance.LoginMsgS2C.password);
        //还原
        PlayerData.Instance.LoginMsgS2C = null;
    }
}

创建 PlayerData.cs 记录当前登录账号的信息

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

public class PlayerData 
{
    private static PlayerData instance = new PlayerData();
    public static PlayerData Instance => instance;

    public Module.LoginMsgS2C LoginMsgS2C;
}

在MsgHelper.cs中实现登录登出消息发送

public static void SendLoginMsg(string username, string pwd)
{
    //实例化对象
    Module.LoginMsgC2S msg = new Module.LoginMsgC2S();
    msg.username = username;
    msg.password = pwd;

    //将对象序列化
    var str = JsonHelper.ToJson(msg);
    Debug.Log(str);

    //发送消息
    SendToService(1002, str);
}

public static void SendLogoutMsg(string username, string pwd)
{
    string tips = "自主下线!!!";
    //触发退出的回调
    Client.Instance.msgManager.logoutHandle?.Invoke(tips);

    //实例化对象
    Module.LogoutMsgC2S msg = new Module.LogoutMsgC2S();
    msg.username = username;
    msg.password = pwd;

    //将对象序列化
    var str = JsonHelper.ToJson(msg);
    Debug.Log(str);

    //发送消息
    SendToService(1004, str);
}

服务端对登录登出消息的处理

创建json对象

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

namespace Service.Modules
{
    internal class Module
    {
        public class RegisterMsgC2S
        {
            public string username;
            public string email;
            public string password;
        }
        public class RegisterMsgS2C
        {
            public string username;
            public string email;
            public string password;
            public int result;//0:注册成功 1:账号已被注册
        }

        public class LoginMsgC2S
        {
            public string username;
            public string password;
        }
        public class LoginMsgS2C
        {
            public string username;
            public string password;
            public int result;//0:登录成功 1:登录失败
        }

        public class ChatMsgC2S
        {
            public string player;
            public string msg;
            public int type;
        }

        public class ChatMsgS2C
        {
            public string player;
            public string msg;
            public int type;
        }

        public class LogoutMsgC2S
        {
            public string username;
            public string password;
        }

        public class LogoutMsgS2C
        {
            public int type;//0:自己下线 1:被人挤掉线
        }
    }
}

实现回调函数

public void LoginHandler(Module.LoginMsgC2S msg)
{
    //查找账号
    foreach (var info in PlayerData.Instance.players)
    {
        if (msg.username == info.username)
        {
            //账号已存在,判断密码是否正确
            if (msg.password == info.password)
            {
                //判断账号是否有账号已登录
                foreach (var onlineInfo in ClientManager.Instance.Clients)
                {
                    //当前已有帐号登录,将上一个账号下线,在登录
                    if (onlineInfo.playerInfo.username == info.username )
                    {
                        //将已登录的账号标记为下线状态
                        onlineInfo.playerInfo.username = "";
                        onlineInfo.playerInfo.isOnline = false;
                        //通知已登录的客端下线
                        MsgHelper.SendLogoutMsg(onlineInfo, 1);
                        break;
                    }
                }
                //标记客户端在线
                client.playerInfo.username = msg.username;
                client.playerInfo.isOnline = true;
                //向客户端发送登录成功
                MsgHelper.SendLoginMsg(client, msg, 0);
            }
            else
            {
                //密码错误,发送登录失败消息
                MsgHelper.SendLoginMsg(client, msg, 1);
            }
            return;
        }
    }
    //账号不存在,发送登录失败消息
    MsgHelper.SendLoginMsg(client, msg, 1);
}

public void LogoutHandler(Module.LogoutMsgC2S msg)
{

    //查找已登录的账号
    foreach (var onlineInfo in ClientManager.Instance.Clients)
    {
        
        if (onlineInfo.playerInfo.username == msg.username)
        {
            //标记下线
            client.playerInfo.username = "";
            client.playerInfo.isOnline = false;
            //发送自主下线成功
            MsgHelper.SendLogoutMsg(client, 0);
            return;
        }
    }
    MsgHelper.SendLogoutMsg(client, 0);
}

 在MsgHelp.cs中实现处理方法

/// <summary>
/// 处理登录业务
/// </summary>
/// <param name="data"></param>
public static void LoginMsgHandler(byte[] data,Action<Module.LoginMsgC2S> aciton)
{
    //数据转化
    var str = Encoding.UTF8.GetString(data);
    var msg = JsonHelper.ToObject<Module.LoginMsgC2S>(str);
    //触发回调
    aciton?.Invoke(msg);
}


/// <summary>
/// 处理退出业务
/// </summary>
/// <param name="data"></param>
/// <param name="aciton"></param>
public static void LogoutMsgHandler(byte[] data, Action<Module.LogoutMsgC2S> aciton)
{
    //数据转化
    var str = Encoding.UTF8.GetString(data);
    var msg = JsonHelper.ToObject<Module.LogoutMsgC2S>(str);
    //触发回调
    aciton?.Invoke(msg);
}

服务端对登录登出结果消息的发送

public static void SendLoginMsg(Client client, Module.LoginMsgC2S msg, int result)
{
    //实例化对象
    Module.LoginMsgS2C registerObj = new Module.LoginMsgS2C();

    registerObj.username = msg.username;
    registerObj.password = msg.password;
    registerObj.result = result;
    //序列化
    string json = JsonHelper.ToJson(registerObj);
    //发送消息
    client.SendToService(1002, json);
}

public static void SendLogoutMsg(Client client,int result)
{
     //实例化对象
    Module.LogoutMsgS2C msg = new Module.LogoutMsgS2C();
    msg.type = result;
    //序列化
    string json = JsonHelper.ToJson(msg);
    //发送消息
    client.SendToService(1004, json);
}

最终效果

Unity-网络编程 笔记3 效果视频

当前项目源码


链接:https://pan.baidu.com/s/1wZE4JAeFjWx6thMRx5D4Dg?pwd=e9vs 
提取码:e9vs 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

shumake

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值