• 一个java程序员自学IOS开发之路(十四)


    上个月实在是太忙了,在系统上线的前几天,业务人员还在不停的提新需求,真是醉了。上线那天晚上一直在出问题,熬到2点才搞定

    2015/12/12

    Day 47

    今天开始学习网络编程

    • 在移动互联网时代,移动应用,只有通过网络进行数据交互,才能保持活力!缺少了数据变化,无论多么华丽的应用,终将变成一潭死水
    • 移动网络应用(良好的UI+良好的用户体验):
      1. 即时通讯:QQ  
      2. 新闻:网易、凤凰新闻
      3. 视频:优酷、百度视频
      4. 音乐:虾米、QQ音乐
      5. 照片:Facebook、Flickr
      6. LBS(基于位置服务):高德、大众点评、墨迹天气、滴滴、快的
      7. 电商:淘宝、天猫、京东
      8. 所罗门SoLoMo(社交+本地化+移动):微信、微博、陌陌、比邻
      9. ……
    • 学习网络编程是开发出优秀网络应用的基础和前提

    UIWebView

    UIWebView是iOS内置的浏览器控件,可以浏览网页、打开文档等

    能够加载html/htm、pdf、docx、txt等格式的文件

    系统自带的Safari浏览器就是通过UIWebView实现的

     

    UIWebView演练——加载百度首页

    很简单,只要调用UIWebView的loadRequest方法即可,如下,

    - (void)viewDidLoad {
    
        [super viewDidLoad];
    
        [self loadURL:@"http://www.baidu.com"];
    
    }
    
     
    
    - (void)loadURL:(NSString *)str {
    
        [self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:str]]];
    
    }

    值得一提的是,现在需要在info.plist中设置使用http请求

    第一步:在plist中添加NSAppTransportSecurity项,此项为NSDictionary

    第二步:在NSAppTransportSecurity下添加

    NSAllowsArbitraryLoads类型为Boolean,value为YES

    苹果正在加大应用安全的管控,这个举措可以看出苹果对信息安全的重视,也暴露出大部分应用传输数据时都是未经过加密的,或使用私有方式加密,以至于苹果开始对开发者提出要求。

    私有加密虽然一定程度上是安全的,但是终究不是一个长久之计。应该早日使用HTTPS确保信息安全!

    • 优点
    1. 使用简单

    NSURL 确定要访问的网络资源

    NSURLRequest 建立网络请求

    1. 能够方便地展现丰富的页面内容
    2. 在开发中,通常遇到不方便排版的内容,会考虑选择UIWebView
    • 缺点
    • HTML为基础的页面方式,交互相对单一,局限性大
    • 编辑排版HTML页面同样需要花费人力

     

    然后配置本地服务器

    通过命令行直接可以使用诸如:C、C++、Python、Ruby、PHP、JavaScript、Perl等语言进行开发,Mac是程序员开发的利器

    Mac系统中上很多程序员使用的工具和软件都是通过命令行实现的,例如:Apache、SQLite、音频格式转换、视频格式转换、SVN、GIT……

     

    判断本地计算机的Apache服务器是否启动

    在浏览器地址栏中输入:localhost

    通过是否有返回结果,即可判断本地的Apache是否正常工作

    然后更改配置文件让服务器支持php

    Mac中,如果要执行系统级命令,或者修改系统级文件,需要通过sudo命令来执行。需要先输入管理员口令才可以执行需要的命令

    启动

    sudo apachectl -k start

    重新启动

    sudo apachectl -k restart

     

    PHP简介

    • PHP(PHP: Hypertext Preprocessor的缩写,中文名:“超文本预处理器”)是一种通用开源脚本语言。入门门槛较低,易于学习,使用广泛,主要适用于Web开发领域
    • 最流行的轻量级Web脚本开发语言之一
    1. PHP 独特的语法混合了 C、Java、Perl 以及 PHP 自创新的语法
    2. PHP可以比CGI或者Perl更快速的执行动态网页——动态页面方面,与其他的编程语言相比:
    3. PHP是将程序嵌入到HTML文档中执行,执行效率比完全生成HTML标记的CGI要高许多
    4. PHP具有非常强大的功能,所有的CGI的功能PHP都能实现
    5. PHP支持几乎所有流行的数据库以及操作系统
    6. 最重要的是PHP可以用C、C++进行程序的扩展

     

    然后安装mysql 

     

    2015/12/13

    Day 48

    昨天把服务器和数据库都配置好了

    今天学习 get&post请求

    先在数据库建表

    drop table if exists userInfo;

    create table userInfo (

    id mediumint not null auto_increment primary key,

        userName varchar(255) not null default '',

        userPwd varchar(255) not null default '',

        sex tinyint not null default 0,

        userImage varchar(255) not null default '',

        location varchar(255) not null default '',

        description varchar(255) not null default '',

        birthday varchar(255) not null default '',

        email varchar(255) not null default '',

        blog varchar(255) not null default '',

        qq varchar(255) not null default '',

        msn varchar(255) not null default ''

    );

     

    insert into userInfo (userName, userPwd) values ('yu3', '123456');

    insert into userInfo (userName, userPwd) values ('zhangsan', 'zhang');

    insert into userInfo (userName, userPwd) values ('lisi', 'li');

    insert into userInfo (userName, userPwd) values ('wangwu', 'wang');

     

    然后利用php发送请求,login.php内容如下

    <?php
    
        
    
        class users {
    
            private $db;
    
            //构造函数 - 建立数据库连接
    
            function __construct() {
    
                $this->db = new mysqli('127.0.0.1', 'root', '123456', 'yu3');
    
                if (mysqli_connect_errno()) {
    
                    printf("连接错误:%s
    ", mysqli_connect_error());
    
                    exit();
    
                }
    
                $this->db->autocommit(FALSE);
    
            }
    
            //析构函数 - 关闭数据库连接
    
            function __destruct() {
    
                $this->db->close();
    
            }
    
            //用户登录
    
            function userLogin() {
    
                if (isset($_GET['username']) && isset($_GET['password'])) {
    
                    //获取参数
    
                    $accessType = '[GET]';
    
                    $userName = $_GET['username'];
    
                    $userPassword = $_GET['password'];
    
                } else if (isset($_POST['username']) && isset($_POST['password'])) {
    
                    //获取参数
    
                    $accessType = '[POST]';
    
                    $userName = $_POST['username'];
    
                    $userPassword = $_POST['password'];
    
                } else {
    
                    echo('非法请求。');
    
                    return false;
    
                }
    
                //设置数据库查询字符编码
    
                $this->db->query('set names utf8');
    
                //查询请求
    
                $data = $this->db->query("select id, userName, sex from userInfo where userName='$userName' and userPwd='$userPassword'");
    
                //绑定查询参数
    
                $this->db->real_escape_string($userName);
    
                $this->db->real_escape_string($userPassword);
    
                //提交查询请求
    
                $this->db->commit();
    
                //提取查询结果
    
                $row = $data->fetch_assoc();
    
                //将查询结果绑定到字典
    
                $result = [
    
                            'userId' => $row['id'],
    
                            'userName' => $row['userName'],
    
                            'sex' => $row['sex']
    
                            ];
    
                //将字典使用JSON编码
    
                echo json_encode($result);
    
                return true;
    
            }
    
        }
    
     
    
    header('Content-Type:text/html;charset=utf-8');
    
    $users = new users;
    
    $users->userLogin();
    
    ?>

    这样在浏览器中发送请求如下,根据zhangsan密码zhang可以查出表里的数据

     

    简历工程,在storyboard里,设置两个textField输入用户名密码,然后点击按钮放松请求,接收的数据在textView里显示

     

    先发送get请求    

    NSLog(@"GET请求开始 %@",[NSThread currentThread]);
    
        NSString *urlStr = [NSString stringWithFormat:@"http://localhost/login.php?username=%@&password=%@", self.userName.text, self.pwd.text];
    
        NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlStr]];
    
        NSLog(@"GET请求-NSURLRequest:%@ %@", request, [NSThread currentThread]);
    
    //     Connection
    
    //     1> 登录完成之前,不能做后续工作!
    
    //     2> 登录进行中,可以允许用户干点别的会更好!
    
    //     3> 让登录操作在其他线程中进行,就不会阻塞主线程的工作
    
    //     4> 结论:登陆也是异步访问,中间需要阻塞住
    
        [NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
    
            if (connectionError == nil) {
    
                NSLog(@"response:%@ %@",response, [NSThread currentThread]);
    
                NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    
                NSLog(@"返回的字符串:%@ - %@", str, [NSThread currentThread]);
    
                [[NSOperationQueue mainQueue] addOperationWithBlock:^{
    
                    self.responseText.text = str;
    
                    NSLog(@“GET请求-更新UI %@",[NSThread currentThread]);
    
                }];
    
            }
    
            NSLog(@"GET请求结束 %@", [NSThread currentThread]);
    
        }];
    
        NSLog(@"GET请求-主线程其他工作 %@", [NSThread currentThread]);

    然后是post请求

     NSLog(@"POST请求开始 %@",[NSThread currentThread]);
    
        NSString *urlStr = @"http://localhost/login.php";
    
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlStr]];
    
        //设置请求类型为POST
    
        request.HTTPMethod = @"POST";
    
        NSString *str = [NSString stringWithFormat:@"username=%@&password=%@", self.userName.text, self.pwd.text];
    
        request.HTTPBody = [str dataUsingEncoding:NSUTF8StringEncoding];
    
        NSLog(@"POST请求-NSMutableURLRequest:%@ %@", request, [NSThread currentThread]);
    
       
    
        [NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
    
            if (connectionError == nil) {
    
                NSLog(@"response:%@ %@",response, [NSThread currentThread]);
    
                NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    
                NSLog(@"返回的字符串:%@ - %@", str, [NSThread currentThread]);
    
                [[NSOperationQueue mainQueue] addOperationWithBlock:^{
    
                    self.responseText.text = str;
    
                    NSLog(@"更新UI %@",[NSThread currentThread]);
    
                }];
    
            }
    
            NSLog(@"POST请求结束 %@", [NSThread currentThread]);
    
        }];
    
        NSLog(@"POST请求-主线程其他工作 %@", [NSThread currentThread]);

    ios9.0以后苹果建议我们使用NSURLSession

    NSURLConnection 作为 Core Foundation / CFNetwork 框架的 API 之上的一个抽象,在 2003 年,随着第一版的 Safari 的发布就发布了。NSURLConnection 这个名字,实际上是指代的 Foundation 框架的 URL 加载系统中一系列有关联的组件:NSURLRequestNSURLResponseNSURLProtocol、 NSURLCache、 NSHTTPCookieStorageNSURLCredentialStorage 以及同名类 NSURLConnection

    NSURLRequest 被传递给 NSURLConnection。被委托对象(遵守以前的非正式协议 <NSURLConnectionDelegate> <NSURLConnectionDataDelegate>)异步地返回一个 NSURLResponse 以及包含服务器返回信息的 NSData

    在一个请求被发送到服务器之前,系统会先查询共享的缓存信息,然后根据策略(policy以及可用性(availability的不同,一个已经被缓存的响应可能会被立即返回。如果没有缓存的响应可用,则这个请求将根据我们指定的策略来缓存它的响应以便将来的请求可以使用。

    在把请求发送给服务器的过程中,服务器可能会发出鉴权查询(authentication challenge,这可以由共享的 cookie 或机密存储(credential storage来自动响应,或者由被委托对象来响应。发送中的请求也可以被注册的 NSURLProtocol 对象所拦截,以便在必要的时候无缝地改变其加载行为。

    不管怎样,NSURLConnection 作为网络基础架构,已经服务了成千上万的 iOS 和 Mac OS 程序,并且做的还算相当不错。但是这些年,一些用例——尤其是在 iPhone 和 iPad 上面——已经对 NSURLConnection 的几个核心概念提出了挑战,让苹果有理由对它进行重构。

    在 2013 的 WWDC 上,苹果推出了 NSURLConnection 的继任者:NSURLSession

    和 NSURLConnection 一样,NSURLSession 指的也不仅是同名类 NSURLSession,还包括一系列相互关联的类。NSURLSession 包括了与之前相同的组件,NSURLRequest 与 NSURLCache,但是把 NSURLConnection 替换成了NSURLSessionNSURLSessionConfiguration 以及 NSURLSessionTask 的 3 个子类:NSURLSessionDataTaskNSURLSessionUploadTaskNSURLSessionDownloadTask

     NSURLConnection 相比,NSURLsession 最直接的改进就是可以配置每个 session 的缓存,协议,cookie,以及证书策略(credential policy,甚至跨程序共享这些信息。这将允许程序和网络基础框架之间相互独立,不会发生干扰。每个NSURLSession 对象都由一个 NSURLSessionConfiguration 对象来进行初始化,后者指定了刚才提到的那些策略以及一些用来增强移动设备上性能的新选项。

    NSURLSession 中另一大块就是 session task。它负责处理数据的加载以及文件和数据在客户端与服务端之间的上传和下载。NSURLSessionTask 与 NSURLConnection 最大的相似之处在于它也负责数据的加载,最大的不同之处在于所有的 task 共享其创造者 NSURLSession 这一公共委托者(common delegate

    我们先来深入探讨 task,过后再来讨论 NSURLSessionConfiguration

    NSURLSessionTask

    NSURLSessionTask is an abstract subclass, with three concrete subclasses that are used directly:NSURLSessionDataTaskNSURLSessionUploadTask, and NSURLSessionDownloadTask. These three classes encapsulate the three essential networking tasks of modern applications: fetching data, such as JSON or XML, and uploading and downloading files.

    NSURLsessionTask 是一个抽象类,其下有 3 个实体子类可以直接使用:NSURLSessionDataTaskNSURLSessionUploadTaskNSURLSessionDownloadTask。这 3 个子类封装了现代程序三个最基本的网络任务:获取数据,比如 JSON 或者 XML,上传文件和下载文件。

     

    当一个 NSURLSessionDataTask 完成时,它会带有相关联的数据,而一个 NSURLSessionDownloadTask 任务结束时,它会带回已下载文件的一个临时的文件路径。因为一般来说,服务端对于一个上传任务的响应也会有相关数据返回,所以NSURLSessionUploadTask 继承自 NSURLSessionDataTask

    所有的 task 都是可以取消,暂停或者恢复的。当一个 download task 取消时,可以通过选项来创建一个恢复数据(resume data,然后可以传递给下一次新创建的 download task,以便继续之前的下载。

    不同于直接使用 alloc-init 初始化方法,task 是由一个 NSURLSession 创建的。每个 task 的构造方法都对应有或者没有completionHandler 这个 block 的两个版本,例如:有这样两个构造方法 –dataTaskWithRequest: 和 –dataTaskWithRequest:completionHandler:。这与 NSURLConnection 的 -sendAsynchronousRequest:queue:completionHandler: 方法类似,通过指定 completionHandler 这个 block 将创建一个隐式的 delegate,来替代该 task 原来的 delegate——session。对于需要 override 原有 session task 的 delegate 的默认行为的情况,我们需要使用这种不带 completionHandler 的版本。

    下面是用NSURLSession重构代码,GET请求

    NSLog(@"GET请求开始 %@",[NSThread currentThread]);
    
        NSString *urlStr = [NSString stringWithFormat:@"http://localhost/login.php?username=%@&password=%@", self.userName.text, self.pwd.text];
    
        NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlStr]];
    
        NSLog(@"GET请求-NSURLRequest:%@ %@", request, [NSThread currentThread]);
    
        
    
        //构造Session
    
        NSURLSession *session = [NSURLSession sharedSession];
    
        NSLog(@"GET请求-NSURLSession:%@ %@",session, [NSThread currentThread]);
    
        NSURLSessionDataTask *task = [session dataTaskWithURL:[NSURL URLWithString:urlStr] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    
            if (error == nil) {
    
                NSLog(@"GET请求-NSURLResponse:%@ %@",response, [NSThread currentThread]);
    
                NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    
                NSLog(@"GET请求-返回的字符串:%@ - %@", str, [NSThread currentThread]);
    
                [[NSOperationQueue mainQueue] addOperationWithBlock:^{
    
                    self.responseText.text = str;
    
                    NSLog(@"GET请求-更新UI %@",[NSThread currentThread]);
    
                }];
    
            }
    
            NSLog(@"GET请求结束 %@", [NSThread currentThread]);
    
        }];
    
        NSLog(@"GET请求-NSURLSessionDataTask:%@ %@",task, [NSThread currentThread]);
    
        [task resume];
    
        NSLog(@"GET请求-主线程其他工作 %@", [NSThread currentThread]);

    控制台打印如下

     

    然后是POST请求,代码如下

    NSLog(@"POST请求开始 %@",[NSThread currentThread]);
    
        NSString *urlStr = @"http://localhost/login.php";
    
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlStr]];
    
        //设置请求类型为POST
    
        request.HTTPMethod = @"POST";
    
        NSString *str = [NSString stringWithFormat:@"username=%@&password=%@", self.userName.text, self.pwd.text];
    
        request.HTTPBody = [str dataUsingEncoding:NSUTF8StringEncoding];
    
        NSLog(@"POST请求-NSMutableURLRequest:%@ %@", request, [NSThread currentThread]);
    
        //构造Session
    
        NSURLSession *session = [NSURLSession sharedSession];
    
        NSLog(@"POST请求-NSURLSession:%@ %@",session, [NSThread currentThread]);
    
        NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    
            if (error == nil) {
    
                NSLog(@"POST请求-NSURLResponse:%@ %@",response, [NSThread currentThread]);
    
                NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    
                NSLog(@"POST请求-返回的字符串:%@ - %@", str, [NSThread currentThread]);
    
                [[NSOperationQueue mainQueue] addOperationWithBlock:^{
    
                    self.responseText.text = str;
    
                    NSLog(@"POST请求-更新UI %@",[NSThread currentThread]);
    
                }];
    
            }
    
            NSLog(@"POST请求结束 %@", [NSThread currentThread]);
    
        }];
    
        NSLog(@"POST请求-NSURLSessionDataTask:%@ %@",task, [NSThread currentThread]);
    
        [task resume];
    
        NSLog(@"POST请求-主线程其他工作 %@", [NSThread currentThread]);

    下面是控制台打印

     

    下面是swift版的代码,GET请求

    print("GET请求-开始-(NSThread.currentThread())")
    
            let urlStr = "http://localhost/login.php?username=(self.userName.text!)&password=(self.pwd.text!)"
    
            let request = NSURLRequest(URL: NSURL(string: urlStr)!)
    
            print("GET请求-request:(request)-(NSThread.currentThread())")
    
            let session = NSURLSession.sharedSession()
    
            let task = session.dataTaskWithURL(NSURL(string: urlStr)!) { (responseData, response, error) -> Void in
    
                if error == nil {
    
                    print("GET请求-response:(response)-(NSThread.currentThread())")
    
                    let str = NSString(data: responseData!, encoding: NSUTF8StringEncoding)!
    
                    print("GET请求-返回字符串:(str)-(NSThread.currentThread())")
    
                    NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
    
                        self.responseText.text = String(str)
    
                        print("GET请求-更新UI-(NSThread.currentThread())")
    
                    })
    
                }
    
            }
    
            task.resume()
    
            print("GET请求-主线程其他工作-(NSThread.currentThread())")

    控制台打印如下

     

    POST请求,代码如下

    print("POST请求-开始-(NSThread.currentThread())")
    
            let urlStr = "http://localhost/login.php"
    
            //设置request
    
            let request = NSMutableURLRequest(URL: NSURL(string: urlStr)!)
    
            request.HTTPMethod = "POST"
    
            let str = "username=(self.userName.text!)&password=(self.pwd.text!)"
    
            request.HTTPBody = str.dataUsingEncoding(NSUTF8StringEncoding)
    
            print("POST请求-request:(request)-(NSThread.currentThread())")
    
            let session = NSURLSession.sharedSession()
    
            let task = session.dataTaskWithRequest(request) { (responseData, response, error) -> Void in
    
                if error == nil {
    
                    print("POST请求-response:(response)-(NSThread.currentThread())")
    
                    let str = NSString(data: responseData!, encoding: NSUTF8StringEncoding)!
    
                    print("POST请求-返回字符串:(str)-(NSThread.currentThread())")
    
                    NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
    
                        self.responseText.text = String(str)
    
                        print("POST请求-更新UI-(NSThread.currentThread())")
    
                    })
    
                }
    
            }
    
            task.resume()
    
            print("POST请求-主线程其他工作-(NSThread.currentThread())")

    控制台打印

    2016/01/08

    Day 48

    今天学习JSON与XML解析,我对这两种数据格式都有所了解,学起来还是很简单的

    JSON的序列化和反序列化

    • 反序列化

    [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];

    • 序列化

    [NSJSONSerialization dataWithJSONObject:array options:0 error:NULL];

     

    NSJSONReadingOptions

    • NSJSONReadingMutableContainers = 1,    根节点可变
    • NSJSONReadingMutableLeaves = 2,          节点可变
    • NSJSONReadingAllowFragments = 4,         根节点可以不是NSDictionary或者NSArray
    • 提示:

    如果枚举类型的起始数值是1,通常0就表示什么选项也不支持,是效率最高的选项

     

    NSXMLParser解析方法

    • NSXMLParser是SAX方法解析
      • SAXSimple API for XML
        • 只能读,不能修改,只能顺序访问,适合解析大型XML,解析速度快
        • 常应用于处理大量数据的XML,实现异构系统的数据访问,实现跨平台
        • 从文档的开始通过每一节点移动,定位一个特定的节点
      • DOMDocument Object Model
        • 不仅能读,还能修改,而且能够实现随机访问,缺点是解析速度慢,适合解析小型文档
        • 一般应用与小型的配置XML,方便操作
        • 为载入到内存的文档节点建立类型描述,呈现可横向移动、潜在巨大的树型结构
        • 在内存中生成节点树操作代价昂贵

    NSXMLParser解析过程

    实例化NSXMLParser,传入从服务器接收的XML数据

    定义解析器代理

    解析器解析

    通过解析代理方法完成XML数据的解析

     

    NSXMLParser解析代理方法

    // 1. 开始解析XML文档

    - (void)parserDidStartDocument:

     

    // 2. 开始解析某个元素,会遍历整个XML,识别元素节点名称

    - (void)parser:didStartElement:namespaceURI:qualifiedName:attributes:

    // 3. 文本节点,得到文本节点里存储的信息数据,对于大数据可能会接收多次!为了节约内存开销

    - (void)parser:foundCharacters:

    // 4. 结束某个节点,存储从parser:foundCharacters:方法中获取到的信息

    - (void)parser:didEndElement:namespaceURI:qualifiedName:

    注意:在解析过程中,2、3、4三个方法会不停的重复执行,直到遍历完成为止

     

    // 5. 解析XML文档结束

    - (void)parserDidEndDocument:

    // 6. 解析出错

    - (void)parser:parseErrorOccurred:

  • 相关阅读:
    数字类型内置方法
    流程控制之while循环
    流程控制之if判断
    基本运算符
    格式化输出的三种方式
    Python与用户交互
    解压缩
    布尔值(bool)
    django基础 -- 8.cookie 和 session
    为博客园文章添加目录的方法
  • 原文地址:https://www.cnblogs.com/yu3-/p/5117001.html
Copyright © 2020-2023  润新知