ASP.NET取得Request URL的各个部分

我們在開發網頁應用程式,時常需要去解析網址(Request.Url)的每個片段,進行一些判斷。例如說 “http://localhost:1897/News/Press/Content.aspx/123?id=1#toc“,我們想要取得網址裡第一層目錄的名字(News)用以判斷不同的頁面標題(Page Title)。

我看很多人都用字串的 IndexOf 方法與 Substring 方法:

Request.Url.PathAndQuery.Substring(1, Request.Url.PathAndQuery.IndexOf(“/”, 1)-1)

這實在太埋沒 .NET 的強大設計了,事實上在 Request 物件就已經提供很多方便的屬性(Property)可供取得網址的片段。

底下這張表就是各種跟 Browser Request 的網址相關的屬性與用法:

網址:http://localhost:1897/News/Press/Content.aspx/123?id=1#toc
Request.ApplicationPath /
Request.PhysicalPath D:\Projects\Solution\web\News\Press\Content.aspx
System.IO.Path.GetDirectoryName(Request.PhysicalPath) D:\Projects\Solution\web\News\Press
Request.PhysicalApplicationPath D:\Projects\Solution\web\
System.IO.Path.GetFileName(Request.PhysicalPath) Content.aspx
Request.CurrentExecutionFilePath /News/Press/Content.aspx
Request.FilePath /News/Press/Content.aspx
Request.Path /News/Press/Content.aspx/123
Request.RawUrl /News/Press/Content.aspx/123?id=1
Request.Url.AbsolutePath /News/Press/Content.aspx/123
Request.Url.AbsoluteUri http://localhost:1897/News/Press/Content.aspx/123?id=1
Request.Url.Scheme http
Request.Url.Host localhost
Request.Url.Port 1897
Request.Url.Authority localhost:1897
Request.Url.LocalPath /News/Press/Content.aspx/123
Request.PathInfo /123
Request.Url.PathAndQuery /News/Press/Content.aspx/123?id=1
Request.Url.Query ?id=1
Request.Url.Fragment
Request.Url.Segments /
News/
Press/
Content.aspx/
123

所以當你看了這張表之後,你還會想用 Request.Url.PathAndQuery.Substring(1, Request.Url.PathAndQuery.IndexOf(“/”, 1)-1) 這種寫法嗎?

用這樣寫 Request.Url.Segments[1].Replace(“/”, “”) 不是又短又直覺嗎? ^_^

以下是產生以上表格的程式碼:

    protected void Page_Load(object sender, EventArgs e)
{

StringBuilder sb = new StringBuilder();

sb.Append(“<table cellpadding=3 cellspacing=0 border=1>”);

sb.Append(“<tr><td colspan=2>”);
sb.Append(“網址:“http://localhost:1897/News/Press/Content.aspx/123?id=1#toc”>http://localhost:1897/News/Press/Content.aspx/123?id=1#toc“);
sb.Append(“</td></tr>”);

// Request.ApplicationPath
        sb.Append(“<tr><td>”);
sb.Append(“Request.ApplicationPath”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.ApplicationPath + “</b>”);
sb.Append(“</td></tr>”);

// Request.PhysicalPath
        sb.Append(“<tr><td>”);
sb.Append(“Request.PhysicalPath”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.PhysicalPath + “</b>”);
sb.Append(“</td></tr>”);

// System.IO.Path.GetDirectoryName(Request.PhysicalPath)
        sb.Append(“<tr><td>”);
sb.Append(“System.IO.Path.GetDirectoryName(Request.PhysicalPath)”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + System.IO.Path.GetDirectoryName(Request.PhysicalPath) + “</b>”);
sb.Append(“</td></tr>”);

// Request.PhysicalApplicationPath
        sb.Append(“<tr><td>”);
sb.Append(“Request.PhysicalApplicationPath”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.PhysicalApplicationPath + “</b>”);
sb.Append(“</td></tr>”);

// System.IO.Path.GetFileName(Request.PhysicalPath)
        sb.Append(“<tr><td>”);
sb.Append(“System.IO.Path.GetFileName(Request.PhysicalPath)”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + System.IO.Path.GetFileName(Request.PhysicalPath) + “</b>”);
sb.Append(“</td></tr>”);

// Request.CurrentExecutionFilePath
        sb.Append(“<tr><td>”);
sb.Append(“Request.CurrentExecutionFilePath”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.CurrentExecutionFilePath + “</b>”);
sb.Append(“</td></tr>”);

// Request.FilePath
        sb.Append(“<tr><td>”);
sb.Append(“Request.FilePath”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.FilePath + “</b>”);
sb.Append(“</td></tr>”);

// Request.Path
        sb.Append(“<tr><td>”);
sb.Append(“Request.Path”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.Path + “</b>”);
sb.Append(“</td></tr>”);

// Request.RawUrl
        sb.Append(“<tr><td>”);
sb.Append(“Request.RawUrl”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.RawUrl + “</b>”);
sb.Append(“</td></tr>”);

// Request.Url.AbsolutePath
        sb.Append(“<tr><td>”);
sb.Append(“Request.Url.AbsolutePath”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.Url.AbsolutePath + “</b>”);
sb.Append(“</td></tr>”);

// Request.Url.AbsoluteUri
        sb.Append(“<tr><td>”);
sb.Append(“Request.Url.AbsoluteUri”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.Url.AbsoluteUri + “</b>”);
sb.Append(“</td></tr>”);

// Request.Url.Scheme
        sb.Append(“<tr><td>”);
sb.Append(“Request.Url.Scheme”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.Url.Scheme + “</b>”);
sb.Append(“</td></tr>”);

// Request.Url.Host
        sb.Append(“<tr><td>”);
sb.Append(“Request.Url.Host”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.Url.Host + “</b>”);
sb.Append(“</td></tr>”);

// Request.Url.Port
        sb.Append(“<tr><td>”);
sb.Append(“Request.Url.Port”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.Url.Port + “</b>”);
sb.Append(“</td></tr>”);

// Request.Url.Authority
        sb.Append(“<tr><td>”);
sb.Append(“Request.Url.Authority”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.Url.Authority + “</b>”);
sb.Append(“</td></tr>”);

// local Request.Url.LocalPath
        sb.Append(“<tr><td>”);
sb.Append(“Request.Url.LocalPath”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.Url.LocalPath + “</b>”);
sb.Append(“</td></tr>”);

// Request.PathInfo
        sb.Append(“<tr><td>”);
sb.Append(“Request.PathInfo”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.PathInfo + “</b>”);
sb.Append(“</td></tr>”);

// Request.Url.PathAndQuery
        sb.Append(“<tr><td>”);
sb.Append(“Request.Url.PathAndQuery”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.Url.PathAndQuery + “</b>”);
sb.Append(“</td></tr>”);

// Request.Url.Query
        sb.Append(“<tr><td>”);
sb.Append(“Request.Url.Query”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.Url.Query + “</b>”);
sb.Append(“</td></tr>”);

// Request.Url.Fragment
        // 原則上你應該無法從 Request.Url.Fragment 取得任何資料,因為通常 Browser 不會送出 #toc 這個部分
        sb.Append(“<tr><td>”);
sb.Append(“Request.Url.Fragment”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.Url.Fragment + “</b>”);
sb.Append(“</td></tr>”);

// Request.Url.Segments
        sb.Append(“<tr>”);
sb.Append(“<td>”);
sb.Append(“Request.Url.Segments”);
sb.Append(“</td>”);
sb.Append(“<td>”);
string[] segments = Request.Url.Segments;
foreach (string s in segments)
{
sb.Append(“<b>” + s + “</b>”);
sb.Append(“<br/>”);
}
sb.Append(“</td>”);
sb.Append(“</tr>”);

sb.Append(“</table>”);

ltlTable.Text = sb.ToString();
}

开源电子书《我的职业是前端工程师》

 

https://juejin.im/entry/58fbfc36b123db6069aa71c6

2017 年 1 月份,看完村上春树的新书《我的职业是一个小说家》,我便萌发了写一个《我的职业是前端工程师》系列文章的想法——以个人视角来看前端领域的各种技术。整个系列的文章大概有 15 篇左右,从我是如何成为一个前端工程师,到各种前端框架的知识。

关注我的微信公众号(扫描下面的二维码或搜索 Phodal).

QRCode

目录

前端技能

来源:https://github.com/phodal/awesome-growth

  • 基础
    • HTML / CSS
    • JavaScript
    • DOM
  • 中级篇
    • 数据格式(如JSON、XML)
    • RESTful API交互(如jQuery Ajax,Fetch API,ReactiveX)
    • 正则表达式
    • HTML语义化
    • 命令行
    • Node.js
    • DIV / CSS
    • SCSS / SASS
    • 矢量图形 / 矢量图形动画(如SVG)
    • 单页面应用
  • 高级篇
    • ES6 / TypeScript
    • CSS3
    • 面向对象编程
    • 函数式编程
    • MVC / MVVM / MV*
    • 安全性(如跨域)
    • 授权(如HTTP Basic、JWT等等)
  • 工程化
    • 代码质量(如JSLint / ESLint / TSLint / CSLint)
    • 代码分析(如Code Climate)
    • 测试覆盖率
    • 构建系统(gulp、grunt、webpack等等)
    • 自动构建(脚本)
  • 兼容性
    • 跨浏览器测试 (Chrome,IE,Firefox,Safari等等)
    • 跨平台测试(Windows、GNU/Linux,Mac OS等等)
    • 跨设备测试(Desktop,Android,iOS,Windows Phone)
    • 跨版本测试(同一个浏览器的不同版本)
  • 前端特定
    • CSS / CSS3 动画
    • JavaScript 动画
    • Web字体嵌入
    • Icon 字体
    • 图形和图表
    • CSS Sprite(如glue)
    • DOM操作(如jQuery、React等等)
    • 模板引擎(如JSX、Handlebars、JSP、Mustache等等)
  • 软件工程
    • 版本管理(如git、svn)
    • 包管理(如npm、bower)
    • 依赖管理
    • 模块化(如CommonJS、WebPack)
  • 调试
    • 浏览器调试
    • Debug工具
    • Wireshark / Charles抓包
    • 远程设备调试(如Chrome Inspect Devices)
  • 测试
    • 单元测试
    • 服务测试
    • UI测试
    • 集成测试
  • 性能与优化
    • PageSpeed / Yslow 优化
    • 加载优化(如gzip压缩、缓存等等)
    • 性能测试(特别是移动Web)
    • 可用性
    • 压缩(如Minify、Uglify、CleanCSS等等)
  • 设计
    • 切页面
    • 线框图(Wireframe)
    • 响应式设计
    • 网格布局(Grid Layout)
    • Flexbox布局
  • SEO
    • Sitemap(站点地图)
    • 内部链接建设
    • MicroData / MicroFormat
    • 页面静态内容生成
    • H1、H2、H3和strong使用
    • Title、Description优化
    • 页面静态内容生成

LICENSE

Phodal's ArticlePhodal's Book

© 2017 Phodal Huang. This code is distributed under the Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 License. See LICENSE in this directory.

待我代码编成,娶你为妻可好

为部署ASP.NET Core准备:使用Hyper-V安装Ubuntu Server 16.10

概述

Hyper-V是微软的一款虚拟化产品,和VMWare一样采用的hypervisor技术。它已经被内嵌到Win10系统内,我们只需要进行简单的安装即可。但是前提是要确保你的机器已经启用虚拟化,可以到任务管理器中查看,如下:

Ubuntu(乌班图)是一个开源的Linux操作系统,同时为企业提供服务器版本。至于其他发行版本如:CentOS、Debian等,这里不是讨论的重点,本篇是以Ubuntu Server 16.10版本进行安装的。且不说Ubuntu资料多,社区广,单凭它是我大学里边接触到的第一任Linux操作系统(先入为主),那么当之无愧的成为了我的首选。

一、安装Hyper-V

1、在控制面板→程序→启用或关闭Windows功能→勾选Hyper-V,然后安装好之后重启计算机

二、配置Hyper-V

1、打开刚才安装好的Hyper-V管理器,右键选择创建虚拟机,然后跟着向导一步一步来

2、修改虚拟机的名字为Ubuntu16.10,然后修改一下虚拟机存储的位置,建议放到空间比较大的一个盘符上

3、选择第一代虚拟机,至于和第二代的区别在哪,请看下图(PS:第二代貌似不支持我的电脑)

4、给它配置一个2G的内存

5、网络适配器没有的话可以暂时先忽略,我们稍后配置,直接下一步。

6、为虚拟机设置一个50G的虚拟硬盘,名称和位置可以默认不做修改

7、选择我们之前下载的Ubuntu16.10 Server版的镜像文件

8、最后一步,完成!

后续也是可以对虚拟机进行设置的,比如把虚拟CPU加到四个核等等

接下来就是配置一个虚拟网络以供虚拟机使用:选择管理器右边的虚拟交换机管理器,打开并创建一个外部虚拟交换机,设置好名称之后选择一个可以访问外网的网络适配器,最后不要忘记将其重新设置为虚拟机的网络适配器

三、安装Ubuntu 16.10 Server版本

1、启动我们的虚拟机,开始安装系统,默认选择英文安装即可,记得要用键盘,鼠标不行!

别问我为什么不选择中文安装,LZ已经亲测没有安装成功,如下图:

2、直接选择安装Ubuntu服务器版,第一个选项

3、语言还是选择英文吧

如果你问我为啥不选择Chinese,因为LZ也已经亲测,会出现乱码,如果你想后续对系统做中文包,就当我没说。

4、接下来你就再也看不到中文了,苟且使用US。

5、不需要配置键盘的,等下选一下就可以了

6、键盘所属国家和布局都选择Chinese,你懂的。

6、然后静静的等待系统的一些相关配置

7、配置你的主机名

8、设置一个账户名称

9、设置一个账户名,然后继续

10、给此账户名设置一个密码然后进行再次验证

11、加密的话就算了。。。

12、设置时钟,如果没问题的话,之后应该会显示是亚洲/上海时区,选择是,然后我们继续

13、配置LVM(百科:LVM全称是逻辑盘卷管理 (Logical Volume Manager),是Linux系统对磁盘分区管理一种机制。

相对于一般的磁盘分区而言LVM是建立在硬盘和分区物理层 之上的一个逻辑层,通过逻辑分区来提高磁盘的利用率)

14、确定选择配置LVM

15、配置你的卷组大小;输入50%,表示一半的逻辑卷组大小

16、确认将分区改动写入磁盘

17、好了,等待安装系统吧

不需要设置代理,继续

不需要更新,以后手动就可以了,之后的软件也直接跳过,然后继续就行了

18、软件安装过程你可以去喝杯水。。。

19、设置GRUB主引导为是

20、大功告成,安装还是很快的。

最后:重启系统之后输入账户和密码登陆,基本上没啥问题了。

写在最后

至此Ubuntu系统的安装告一段落,来来回回折腾了好几次。可惜的是官方已经可以升级到17.04(囧),如果你需要长期支持的话,建议还是安装Ubuntu Server 16.04 LTS!只是这里作为学习和实践为目的的,所以也就无所谓了。接下来主要是部署我们的ASP.NET Core项目,这个才是重中之重。😁

关于php 高并发解决的一点思路

涉及抢购、秒杀、抽奖、抢票等活动时,为了避免超卖,那么库存数量是有限的,但是如果同时下单人数超过了库存数量,就会导致商品超卖问题。那么我们怎么来解决这个问题呢,我的思路如下(伪代码):

sql1:查询商品库存
if(库存数量 > 0)
{
//生成订单…
sql2:同时库存-1
}

当没有并发时,上面的流程看起来是再正常不过了,假设同时两个人下单,而库存只有1个了,在sql1阶段两个人查询到的库存都是>0的,于是最终都执行了sql2,库存最后变为-1,超售了,这不是我们想要的结果吧。

解决这个问题比较流行的思路我总结了下:
1.用额外的单进程处理一个队列,下单请求放到队列里,一个个处理,就不会有并发的问题了,但是要额外的开启后台进程以及延迟问题,这里暂不予考虑。这里我可使用消息队列,我们常用到Memcacheq、Radis。 比如:有100张票可供用户抢,那么就可以把这100张票放到缓存中,读写时不要加锁。 当并发量大的时候,可能有500人左右抢票成功,这样对于500后面的请求可以直接转到活动结束的静态页面。进去的500个人中有400个人是不可能获得商品的。所以可以根据进入队列的先后顺序只能前100个人购买成功。后面400个人就直接转到活动结束页面。当然进去500个人只是举个例子,至于多少可以自己调整。而活动结束页面一定要用静态页面,不要用数据库。这样就减轻了数据库的压力。
2.mysql乐观锁,意思是比如总库存是2,抢购事件提交时,立马将库存+1,那么此时库存是3,然后订单生成后,在更新库存前再查询一次库存(因为订单生成理所当然库存-1,但是先不急,再查一次库存返回结果是3),看看跟预期的库存数量(这里预期的库存是3)是否保持一致,不一致就回滚,提示用户库存不足。这里说道悲观锁,可能有朋友会问,那一定有乐观锁了吧??这里我就浅谈下我所了解的悲观与乐观锁了

悲观锁与乐观锁是两种常见的资源并发锁设计思路,也是并发编程中一个非常基础的概念。本文将对这两种常见的锁机制在数据库数据上的实现进行比较系统的介绍。

悲观锁(Pessimistic Lock)


悲观锁的特点是先获取锁,再进行业务操作,即“悲观”的认为获取锁是非常有可能失败的,因此要先确保获取锁成功再进行业务操作。通常所说的“一锁二查三更新”即指的是使用悲观锁。通常来讲在数据库上的悲观锁需要数据库本身提供支持,即通过常用的select … for update操作来实现悲观锁。当数据库执行select for update时会获取被select中的数据行的行锁,因此其他并发执行的select for update如果试图选中同一行则会发生排斥(需要等待行锁被释放),因此达到锁的效果。select for update获取的行锁会在当前事务结束时自动释放,因此必须在事务中使用。

这里需要注意的一点是不同的数据库对select for update的实现和支持都是有所区别的,例如oracle支持select for update no wait,表示如果拿不到锁立刻报错,而不是等待,mysql就没有no wait这个选项。另外mysql还有个问题是select for update语句执行中所有扫描过的行都会被锁上,这一点很容易造成问题。因此如果在mysql中用悲观锁务必要确定走了索引,而不是全表扫描。

乐观锁(Optimistic Lock)


乐观锁的特点先进行业务操作,不到万不得已不去拿锁。即“乐观”的认为拿锁多半是会成功的,因此在进行完业务操作需要实际更新数据的最后一步再去拿一下锁就好。

乐观锁在数据库上的实现完全是逻辑的,不需要数据库提供特殊的支持。一般的做法是在需要锁的数据上增加一个版本号,或者时间戳,然后按照如下方式实现:

复制代码
1. SELECT data AS old_data, version AS old_version FROM …;
2. 根据获取的数据进行业务操作,得到new_data和new_version
3. UPDATE SET data = new_data, version = new_version WHERE version = old_version
if (updated row > 0) {
    // 乐观锁获取成功,操作完成
} else {
    // 乐观锁获取失败,回滚并重试
}
复制代码

乐观锁是否在事务中其实都是无所谓的,其底层机制是这样:在数据库内部update同一行的时候是不允许并发的,即数据库每次执行一条update语句时会获取被update行的写锁,直到这一行被成功更新后才释放。因此在业务操作进行前获取需要锁的数据的当前版本号,然后实际更新数据时再次对比版本号确认与之前获取的相同,并更新版本号,即可确认这之间没有发生并发的修改。如果更新失败即可认为老版本的数据已经被并发修改掉而不存在了,此时认为获取锁失败,需要回滚整个业务操作并可根据需要重试整个过程。好吧,在此唠叨总结下这两个锁:

总结

  • 乐观锁在不发生取锁失败的情况下开销比悲观锁小,但是一旦发生失败回滚开销则比较大,因此适合用在取锁失败概率比较小的场景,可以提升系统并发性能
  • 乐观锁还适用于一些比较特殊的场景,例如在业务操作过程中无法和数据库保持连接等悲观锁无法适用的地方

3.根据update结果来判断,我们可以在sql2的时候加一个判断条件update table set 库存=xxx where 库存>0,如果返回false,则说明库存不足,并回滚事务。
4.借助文件排他锁,在处理下单请求的时候,用flock锁定一个文件,如果锁定失败说明有其他订单正在处理,此时要么等待要么直接提示用户”服务器繁忙”

大致代码如下:
阻塞(等待)模式

复制代码
<?php
$fp = fopen("lock.txt", "w+");
if(flock($fp,LOCK_EX))   //锁定当前指针,,,
{
  //..处理订单
  flock($fp,LOCK_UN);
}
fclose($fp);
?>
复制代码

非阻塞模式

复制代码
<?php
$fp = fopen("lock.txt", "w+");
if(flock($fp,LOCK_EX | LOCK_NB))
{
  //..处理订单
  flock($fp,LOCK_UN);
}
else
{
  echo "系统繁忙,请稍后再试";
}
 
fclose($fp);
?>
复制代码

5.如果是分布式集群服务器,就需要一个或多个队列服务器 小米和淘宝的抢购还是有稍许不同的,小米重在抢的那瞬间,抢到了名额,就是你的,你就可以下单结算。而淘宝则重在付款的时候的过滤,做了多层过滤,比如要卖10件商品,他会让大于10的用户抢到,在付款的时候再进行并发过滤,一层层的减少一瞬间的并发量。

6.使用redis锁 product_lock_key 为票锁key 当product_key存在于redis中时,所有用户都可以进入下单流程。 当进入支付流程时,首先往redis存放sadd(product_lock_key, “1″),如果返回成功,进入支付流程。如果不成,则说明已经有人进入支付流程,则线程等待N秒,递归执行sadd操作。

当然类似于淘宝双11的疯抢架构远远比我说滴这些复杂多啦….更多解决方案需要不停滴去实战中获取心得….大家有好的解决思路清随时共享留言哈

 

无论从事什么行业,只要做好两件事就够了,一个是你的专业、一个是你的人品,专业决定了你的存在,人品决定了你的人脉,剩下的就是坚持,用善良專業和真诚赢取更多的信任。不忘初心 方得始终!

Charles 手机抓包连接教程,亲身试过

最近使用Charles抓包,在网上搜教程,很多教程都不完整,弄了好久才弄好连接手机抓包功能,这次自己整理一下,分享出来,也便于以后自己使用,下面开始吧。

1、     安装Charles

破解安装包地址:http://pan.baidu.com/s/1kUUj2gn

包含证书和破解jar包

2、安装好,Charles之后,进行配置,要确保在一个wifi

环境中,使用ifconfig en0查看电脑连接wifi所用的ip:

或者在打开网络偏好设置中,查看电脑的ip地址:

3、打开CharlesProxy->Proxy Settings,选中Enabel transparent HTTPproxying。点击ok。Port一般都是8888,这个要和下一步手机中的端口相同。

4、打开手机,设置->无线局域网下的HTTP代理,选择手动:

5、点击返回,电脑中Charles会出现一个提示框,点击allow。

如果没有出现点击Proxy->Access control settings,添加你的手机ip。

6、现在我们用我们的iPhone打开 safari中输入这个网址,安装描述文件。

IOS9输入:

https://www.charlesproxy.com/getssl

其他:

http://www.charlesproxy.com/getssl

注:IOS9开始使用https

下载描述文件安装。

如果提示连接网络失败关闭电脑wifi重新打开一次,并重启Charles就可以了。