摘要: 很久没有回来这里写技术BLOG了,这里的氛围还行,大家都对一个问题积极的思考(至少之前这里给我的感觉是这样的),2年里面自己也忙着做些事情,没有写,最近有空也就写写,偶尔会去oschine.net看看新闻,然后就在那里看到了一个人提出的问题很有意思,就是怎么表达式求解,例如(1 + 2) / 3 - 1 * 2 + 5 / (3 + 2)这样的字符串输入,怎么样解析之后输出结果。说来也好笑,对于我...
阅读全文
posted @
2011-11-09 10:36 ruislan 阅读(1743) |
评论 (6) |
编辑 收藏
摘要: 很久没来了,不是一位朋友给我发邮件问我关于swing的问题,才想起,然后翻看了之前的代码,发现当年还实现了一个Vista风格的按钮没有放出来,现在补上,也许现在人们的swing水平对我这代码不屑一顾,不过还是依然抛砖引玉,给未知的人们一个启发。还是老惯例,上效果图:
正常情况下:(和vista一样具有焦点的按钮的颜色渐深渐浅的循环变化)
鼠标在区域内时:
鼠标按下去:
然后是代...
阅读全文
posted @
2009-09-12 12:54 ruislan 阅读(2472) |
评论 (3) |
编辑 收藏
认为自己是达人的就不用看了。只是一点小技巧,不敢班门弄斧,做个总结,为那些还不知道的解解惑,随便告诉大家我还活着。
最近客户提了个小改动,客户网站上图片存放的目录需要改动一下。例如在网上访问是www.blogjava.net/images/*.*,在服务器上的目录是D:/<webroot>/images/*.*,客户想把这个images目录下的资源全部移动到E:/data/里面去,但是在网上www.blogjava.net/images/*.*还是同样可以访问得到,我刚开始犯了形式主义的错误,老是想用程序解决,一会filter,一会servlet/action,后来我配置程序的时候突然看到了server.xml,于是我想到了选择用映射的方式。正好,server.xml中的<Context>就是做这个事情的。于是乎我们在<Host></Host>中增加了一个<Context docBase="E:/data/images" path="/images">,OK,重启之后,所有检索www.blogjava.net/images路径下的资源实际上都由E:/data/images下的资源提供了。
posted @
2008-02-15 16:41 ruislan 阅读(1259) |
评论 (3) |
编辑 收藏
在写多线程程序的时候,你就像个经理,手下有那么或多或少的职员,你负责协调职员之间的工作,如果你稍不留神,职员之间就陷入了相互等待的尴尬状态。还好,大多数时候多线程都还在我们掌控之内,即便是遇到这样的deadlock情况,我们也能够去修正,但是有的时候生活就是那么不尽人意,特别是NIO这种你不能掌控的时候,且看下面的代码:
/**
* @(#)DeadLock.java v0.1.0 2007-12-13
*/
package ruislan.rswing.test;
import java.net.InetSocketAddress;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.concurrent.Executors;
/**
* NIO DeadLock
*
* @author ruislan <a href="mailto:z17520@126.com"/>
* @version 0.1.0
*/
public class DeadLock {
public static void main(String[] args) throws Exception {
Service service = new Service();
Executors.newSingleThreadExecutor().execute(service);
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);
channel.connect(new InetSocketAddress("http://www.blogjava.net", 80));
service.addChannel(channel);
}
static class Service implements Runnable {
Selector selector;
public Service() {
}
public void run() {
try {
selector = Selector.open();
while (true) {
selector.select();
System.out.println(selector.selectedKeys().size());
}
} catch (Exception e) {
}
}
public void addChannel(SocketChannel channel) {
try {
channel.register(selector, SelectionKey.OP_CONNECT
| SelectionKey.OP_READ);
System.out.println("can reach here?when pigs fly!");
} catch (ClosedChannelException e) {
e.printStackTrace();
}
}
}
}
乍看之下,我们的代码没有问题,但是运行之后你会发现,这句System.out.println("can reach here?when pigs fly!");永远无法执行,也就是说register()方法被阻塞了!Oh god bless,让我们看看JavaDoc是怎么说的:
...
可在任意时间调用此方法。如果调用此方法的同时正在进行另一个此方法或 configureBlocking 方法的调用,则在另一个操作完成前将首先阻塞该调用。然后此方法将在选择器的键集上实现同步,因此如果调用此方法时并发地调用了涉及同一选择器的另一个注册或选择操作,则可能阻塞此方法的调用。
...
看这句“可在任意时间调用此方法。”,也就是说我们调用的时间没有任何限制,而阻塞的情况只会出现在“如果调用此方法的同时正在进行另一个此方法或 configureBlocking 方法的调用”的情况下,即便是阻塞了,我相信“正在进行另一个此方法或configureBlocking”也不会花掉太多的时间,况且这里没有上面这样的情况出现。那register()是被谁挡住了?或者是BUG?
我们来分析一下程序,程序有两个线程主线程和Service线程,主线程启动后启动了Service线程,Service线程启动Selector然后Service线程陷入select()的阻塞中,同时,主线程调用Service的addChannel()方法来添加一个SocketChannel,嗯,两个线程之间唯一的联系就是selector,看来要从selector寻找线索,很可惜,selector的实现没有源代码可查,不过可以肯定是channel的register()会调用selector的register(),虽然此时持有selector的Service线程被select()方法所阻塞,但是并不影响其他线程对其操作吧?那么,剩下的解释就是Selector的select()方法和register()方法公用了一个锁,select()方法阻塞住了,所以register()拿不到这个锁了,那么这样一来我们就只能保证让select()或者register()不能同时调用或者register()调用的时候select()不持有这个锁,也就是说我们要用Service线程自己来执行addChannel()方法,所以改进如下:
/**
* @(#)DeadLock.java v0.1.0 2007-12-13
*/
package ruislan.rswing.test;
import java.net.InetSocketAddress;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* NIO DeadLock
*
* @author ruislan <a href="mailto:z17520@126.com"/>
* @version 0.1.0
*/
public class DeadLock {
public static void main(String[] args) {
Service service = new Service();
new Thread(service).start();
for (int i = 0; i < 5; i++) {
new Thread(new ChannelAdder(service)).start();
}
}
static class ChannelAdder implements Runnable {
private Service service;
public ChannelAdder(Service service) {
this.service = service;
}
@Override
public void run() {
try {
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);
channel.connect(new InetSocketAddress(
"http://www.blogjava.net", 80));
service.addChannel(channel);
} catch (Exception e) {
e.printStackTrace();
}
}
}
static class Service implements Runnable {
private Selector selector;
private Queue<SocketChannel> pendingRegisters;
public Service() {
pendingRegisters = new LinkedBlockingQueue<SocketChannel>();
}
public void run() {
try {
selector = Selector.open();
while (true) {
selector.select();
System.out.println(selector.selectedKeys().size());
handlePendingRegisters();
}
} catch (Exception e) {
}
}
public void handlePendingRegisters() {
while (!pendingRegisters.isEmpty()) {
SocketChannel channel = pendingRegisters.poll();
try {
channel.register(selector, SelectionKey.OP_CONNECT);
System.out.println("can reach here?yeah!");
} catch (ClosedChannelException e) {
e.printStackTrace();
}
}
}
public void addChannel(SocketChannel channel) {
pendingRegisters.offer(channel);
selector.wakeup();
}
}
}
新的代码,我们在Service的线程提供了一个待处理Channel队列,然后在添加一个SocketChannel到队列中时唤醒这个selector,取消阻塞,然后在Service的循环中处理这个pendingChannel,这样就避免这个Deadlock的发生了。当然我们亦可以在那个代码上将select的超时时间设置非常的短,然后让两个线程去竞争,这样做有太多的不可控性,不推荐了。
posted @
2007-12-13 18:31 ruislan 阅读(1337) |
评论 (3) |
编辑 收藏
UI作为用户与电脑的交互界面,如何更好的服务于人,让人们用起来方便、简单、快捷一直是UI开发者应该有的觉悟,作为开发人员的我们来说,不应该只是把UI推给电脑平面设计人员,更不应该一手包办了(如果你不是一个人的话)。我们开发人员常常在开发UI的时候避重就轻,基本上都在强调code的美学,模式的应用而忽略了真实用户的感受。我们常常得意于自己技术的美丽,而将一些比自己水平低的应用嗤之以鼻。但是用户却从来不关心代码是如何写的,他们关心这个应用是否对他们有用,顺手乎?聪明乎?所以如果我们只是美丽于自己的设计,太关注软件的本身而忽略了用户的感受,就跟某些象牙塔里拿着钱做些无用的研究的人没什么两样,或许有个美丽的名词,为了科学。
那么如何才能算是好的UI人性化的设计呢?这个得看针对的用户主要是哪些。我们熟知的操作系统Windows XP,Windows Vista,Vista是微软最新的操作系统,包含了很多开发人员辛苦的结晶,但是在我身边的很多人都不愿意装它,也包括一些新闻的调查也说Vista不如当年XP出世那般火爆,他们大多数不愿装的都说了同样的话,XP都还有很多不懂,怕Vista更搞不懂,说实话我用过Vista,就我这么一个算是业内人士用起来当然驾轻就熟,再加上我们都有勇于创新的精神,所以常常去用新的东西,而普通客户就不这么想了,我问了几个不懂电脑才安装了Vista的用户的感受,“开始菜单的‘开始’两个字没有了,我还以为换了位置”,“界面比XP漂亮啊,但是我的机器好像有点慢,是不是要设置个什么啊”……再来我们熟知的AJAX,我已经接到过很多次不同的人给我的电话,说“为什么网页打开的时候突然好卡了,以前不这样啊?”,“网页浏览不了,老说请稍候,数据加载中,等了很久,就是不出现”……面对这些电话或许我们会说,你们怎么那么笨啊,它卡是因为在下东西,在执行JS,写JS的人太垃圾,浪费了资源,不出现就刷新啊,不要浏览那个不专业的网站了,等等就OK了等等回答,其实很多时候我们可以避免用户的问题出现,例如你的AJAX的JS太大的时候,可以先提示用户说,数据量较大,请稍后,如果长时间无反应,请按浏览器的刷新按钮,或者尝试按下F5键。
我还见过许多软件鼓吹自己的功能如何强大,如何厉害,多么的人性化,但是我打开他们的软件,居然发现只能用鼠标操作!!这是多么大的UI设计失败!在举一个例子,MSN和QQ两个IM,如果你用MSN,在联系人框里按上下的话,MSN会很聪明的明白你是要选择上一位或者下一位联系人,而QQ会很聪明的明白你是要拖动滑动条!@#$,还有很多软件记忆力太差,不管我如何操作,它就是记不住,关闭软件重新启动后又回到了最初的模样,还有的软件自信心不足,一再问我“你确定吗?”,“真的要这样做吗?”,“或许您不小心点了?”而我只是在点关闭这个娱乐性质的软件而已,而有些软件又特胆肥,做了一个不可恢复的操作尽然连提示都没有,还有的软件文化太差,常常把一个按钮或者图标该表达的含义弄得模棱两可,以至于常常让我们会错意,做错操作,或者把一些高风险的操作放在常用操作的旁边,很容易点错,还有的把不常用的操作也放到常用操作区,还不告诉用户怎么去掉,这样的例子不胜枚举。
出现这些问题的原因在于我们与用户之间的思维方式有着很大的不同。例如在写文章之前我才将老爸从我的电脑椅上请下来,请下来之前他正在看我吃饭前的网页——“界面九宫格”,我说您能看得懂嘛,他说“不懂,不过这软件的界面不都这样嘛?再说了,一张纸就8个方位,加上中间正好九个,你的东西不摆这里摆哪里啊?”,我正要解释一下这与软件设计的关系,但是突然一想,是啊,有道理啊,要是我给他老人家再解释一下可以放在上面和下面,那不就是3D的了。再比如我一直都很不屑一顾的网络实名,但是当它被我在很多人的机器里面消灭之后,很多人都打电话问我,怎么在地址栏里面输入汉字,跳出搜索界面了,不是那个汉字的网站了,以前是有的,原来我只看到了它流氓的一面,忽略了普通用户是根本记不住网址在哪里,甚至有些用户不懂英文,你怎么让他记得住全是英文的网址呢?不过过了几天,他们都说不用了,有一个网站导航网址做了他们的主页,他们平日想去的网站都在上面列着的,我后来才知道,就是被我以前同寝室的删除了半天的hao123。所以我们必须充分考虑我们的应用是针对哪些用户,他们是哪一类人,习惯是什么,当然还有就是UI设计的一些基本的东西,例如鼠标能够完成的动作,同样键盘也能完成等等。
posted @
2007-11-11 13:36 ruislan 阅读(1421) |
评论 (5) |
编辑 收藏
Swing作为一个完整的UI解决方案,包含了一个GUI程序所拥有的方方面面,当然包括作为普通程序也好,作为GUI程序也好,作为Web程序等等程序都共有的线程概念。
Swing中的线程有三种:初始线程,事件线程,工作线程
这三种线程基本上包括了让一个GUI完美工作的方方面面,首先,初始线程被用来创建GUI组件、资源加载和启动GUI组件,众所周知,Swing是事件驱动的,所以当UI出现了之后,初始线程就完成了它的使命,并将接力棒交给了事件线程,Event Dispatch Thread,这个时候所有组件的事件行为都交给了这个线程去处理,当然我们自己也要需要用线程来运行许多任务,优秀的GUI程序是绝不能让界面被卡死不动的,那会让用户崩溃,所以这个时候就需要工作线程了,也可以说是在背后运行的线程,这种线程是劳动阶级,任劳任怨的执行者长时间的工作。
初始线程的写法很简单,这样就可以了:
SwingUtilities.invokeLater(new Runnable() {
public void run() {
initGUI();
showGUI();
}
}
但是Applet中,你可能需要调用SwingUtilities.invokeAndWait这个方法,要是init方法返回了,浏览器开始展现Applet,但是GUI的创建还在thread中,出错也是可想而知的。
至于invokeLater和invokeAndWait这两个线程的简单点的区别就是invokeLater是异步的,你不知道它什么时候会开始执行,invokeAndWait则是同步的,它会等到动作执行完成之后才返回。
Event Dispatch Thread不是线程安全的,所以要用线程来与它打交道要注意了,同步问题总是让人头痛。
在1.5之前应该说工作线程都是由开发人员自己去定义的,但是现在Swing推荐了SwingWorker这个类,包括Swing最新的符合JSR标准的Swing AppFramework也使用了SwingWorker这个类来处理所有在GUI背后做的事情。
了解了Swing中的线程定义,能够让我们更好的写出优美的基于Swing的GUI程序。
posted @
2007-11-04 12:40 ruislan 阅读(1257) |
评论 (2) |
编辑 收藏
很多人都说我们这行的人是偏执狂,我也觉得我是有一点倔脾气,就像看连续剧,从第一集开始一直到最后一集才会关上电脑,一旦一个研究开始,就一定要有一个结果或者令自己满意的结果才结束,但是当我看到这位“偏执狂”之后,我觉得我只是稍微有一点偏执而已。
研究UI绘制的时候很容易陷入另外一个领域,图像领域,或者是游戏领域,我不喜欢做游戏是因为我爱玩游戏,如果玩和工作绑在一起了的话,那么工作之后的休闲就也是工作了。
好了,说了点废话,下面是对FengGUI的介绍.
FengGUI是一个建立在OpenGL上的GUI的API,FengGUI提供了很多标准的UI组件,像Button,TextField,Panel之类的,下面先看看截图:
GridLayout的截图
可分割的面板
要说到最大的特色,莫过于FengGUI基于OpenGL,并且可以在组件里面直接使用OpenGL,可以轻松的集成jME(java Monkey Engine,一个非常棒的Java 3D游戏引擎),jogl(Java OpenGL API),lwjgl(轻量级Java游戏库),jPCT(同样非常棒的Java 3D游戏引擎),但是在跑它的demo时我也感受到了CPU 80% 工作率的压力,所以就目前的我肤浅的了解,用它来做普通的GUI程序估计还为时有点早,但是如果是游戏中的组件的话确实是与上述引擎和API的非常好的补充。
posted @
2007-10-27 20:34 ruislan 阅读(1697) |
评论 (2) |
编辑 收藏
以前或许大家对一个UI组件是否透明没有那么关心,但是自从Vista的毛玻璃出现后,UI透明就成了大家非常关注的一个话题,于是Java阵营开始了铺天盖地的讨论如何实现透明的效果,但是很不幸的是无论组件如何透明和变换,但是能够放置于屏幕任何位置的Window一族就是没法透明和变形,原生代码的问题还是交给原生代码来解决吧。
自己写原生代码是可怕的,特别是对我这种只知道Java的平凡程序员,所以我们得借助一个非常方便的跨平台的调用OS function方便的Lib,JNA便是最佳选择(那个谁,这里不是讨论JRuby&JPython的)。
so, all robots, transform!
下面我们要做一个界面是圆角四边形的,中间有一个滑动条来滑动调节透明度。先看看做好的截图。
注意图中的JFrame边角已经变成了圆弧,滑动滑块,JFrame开始透明,桌面的图标显现出来,下面是代码。
/**
* @(#)TransparentFrame.java v0.1.0 2007-10-21
*/
package ruislan.rswing.test;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.RoundRectangle2D;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.border.LineBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import com.sun.jna.examples.WindowUtils;
/**
* Transparent JFrame use JNA
*
* @author ruislan <a href="mailto:z17520@126.com"/>
* @version 0.1.0
*/
public class TransparentFrame {
private JFrame frame;
private JPanel container;
private JSlider slider;
private JPanel titleBar;
private JLabel titleLabel;
private JButton closeButton;
public static void main(String[] args) {
new TransparentFrame().launch();
}
private void launch() {
createUI();
launchUI();
}
protected void launchUI() {
frame.setVisible(true);
}
protected void createUI() {
System.setProperty("sun.java2d.noddraw", "true");
frame = new JFrame();
frame.setSize(200, 150);
frame.setAlwaysOnTop(true);
frame.setUndecorated(true);
container = new JPanel();
frame.setContentPane(container);
container.setLayout(new BorderLayout());
container.add(new JLabel("Ubunto vs Vista, I like both."),
BorderLayout.CENTER);
container.setBorder(new LineBorder(Color.BLACK));
titleBar = new JPanel();
titleBar.setLayout(new BorderLayout());
titleLabel = new JLabel("JNA is great!");
titleBar.add(titleLabel, BorderLayout.CENTER);
titleBar.setBorder(new LineBorder(Color.GRAY));
closeButton = new JButton("X");
closeButton.setFocusPainted(false);
closeButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
titleBar.add(closeButton, BorderLayout.EAST);
container.add(titleBar, BorderLayout.NORTH);
slider = new JSlider(0, 100);
slider.setValue(100);
slider.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
float value = slider.getValue();
WindowUtils.setWindowAlpha(frame, value * 0.01f);
}
});
container.add(slider, BorderLayout.SOUTH);
RoundRectangle2D.Float mask = new RoundRectangle2D.Float(0, 0, frame
.getWidth(), frame.getHeight(), 20, 20);
WindowUtils.setWindowMask(frame, mask);
centerWindow(frame);
}
public static void centerWindow(Container window) {
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
int w = window.getSize().width;
int h = window.getSize().height;
int x = (dim.width - w) / 2;
int y = (dim.height - h) / 2;
window.setLocation(x, y);
}
}
利用JNA来制作透明效果非常简单,只需要调用
WindowUtils.setWindowAlpha(window, alpha)就可以了。当然这是JNA特别为UI写的工具代码。如果要改变形状,用WindowUtils.setWindowMask(window, shape)或者WindowUtils.setWindowMask(window, icon)就可以了,但是要注意一点必须设置System.setProperty("sun.java2d.noddraw", "true"),否则JNA会告诉你这个程序不支持透明。当然要运行起来,还得需要两个Jar,jna.jar 和 examples.jar ,都不是很大,只有200多K。
虽然这篇文章只是介绍了一下JNA关于Swing的简单用法,但是有了这个我最先到的便是可以做类似于Yahoo Widget和Google Widget之类的东西了,还可以做好看的fishEye,SideBar,JNA的JAR两个合起来不过400K,却能让这么多复杂的事情简单化,真是精湛的艺术啊,嗯。
posted @
2007-10-21 13:43 ruislan 阅读(4364) |
评论 (12) |
编辑 收藏
很久没有上来更新了,因为一些原因。但是,我又回来了,套用MASK的话,小子们,想我吗?
回来了先报告一个Blogjava的BUG,就是用Opera9浏览器写文章时不能用鼠标点击编辑区,还好用TAB可以切换过来,然后就是用Google输入法的话,标点符号和数字输入一次,编辑区会出来两个,最后就是编辑完之后好像内容也保存不了,所以我又换回FF来编辑了。
回来了却不知从何开始了,组件的改造也算抛砖引玉了(当然如果你们喜欢,我还可以继续改造),冰封的程序要继续改造的话就要开一个工程了大家集体参与了,所以那就还是先从上次哪位仁兄提到的组件透明的问题,从那个开始吧!show time。
posted @
2007-10-20 16:30 ruislan 阅读(423) |
评论 (1) |
编辑 收藏
昨天我们改进了选择区,今天我们来继续为选择区选定之后添加一个操作框
下面是改进后的截图:
正常的情况,右下角出现了一个操作框
在三个(左、右、底)边际的情况,我们重新计算了位置。
三个按钮还没有功能,也不是图片,但是我们离QQ的截屏程序又进一步了,嗯!
posted @
2007-09-14 18:48 ruislan 阅读(1145) |
评论 (10) |
编辑 收藏
“千里冰封” 兄弟的截屏程序酷毙了,但是好像9月4日之后就没有继续更新了,我们来继续为他的程序改进,顺便也把我们这几天都在讲的2D绘制用进来,我们的目标是让冰封的截屏程序成为截屏程序里的王!
今天先改进一下截图时候的选框,还是先放上截图的截图(*o*):
这是原来的图片,下面是改进后的
和改进的代码部分:
这部分代码插入 Temp类的paintComponent方法中的 if (showTip) 这句的前面
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, 0.3F));
g2d.setColor(Color.RED.brighter().brighter());
int sX = Math.min(startX, endX);
int sY = Math.min(endY, startY);
g2d.fillRect(sX, sY, Math.abs(endX - startX), Math.abs(endY
- startY));
g2d.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, 1F));
boolean drawCTip = endX - startX != 0 && endY - startY != 0;
if (drawCTip) {
String cTip = String.format("%dX%d", Math.abs(endX - startX),
Math.abs(endY - startY));
int cTipH = 20;
Font cTipFont = new Font("system", Font.BOLD, 16);
g2d.setFont(cTipFont);
int cTipW = SwingUtilities.computeStringWidth(
getFontMetrics(cTipFont), cTip);
g2d.setPaint(Color.BLACK);
int cStartY = sY - cTipH > 0 ? sY - cTipH : sY;
g2d.fillRect(sX, cStartY, cTipW, cTipH);
g2d.setPaint(Color.WHITE);
g2d.drawString(cTip, sX, cStartY == sY ? sY + cTipH - 3
: sY - 3);
}
g2d.dispose();
怎么样,比起QQ的截图程序,我们又近一步了,嗯。
posted @
2007-09-13 13:28 ruislan 阅读(1482) |
评论 (10) |
编辑 收藏