摘 要
本文所报告的项目是一个 智能图片分享的应用——PiliPili;
它以AndroidStudio为开发工具,使用GitHub作为项目管理,以Bmob云数据库作为数据源,还整合了例如Glide、AVLoadingIndicatorView等强大的功能框架,它以Element为底层的风格设计,在美观大方的基础上,实现了强大的功能;
它以卡片的显示,展示图片和想说的话,也可以查看到用户头像与昵称等信息,还可以完成点赞与分享功能;
它几乎可以满足你的所有应用需求:账户注册、登录,查看首页板块,点赞功能,分享功能,发布你自己的卡片(图片)信息,用户管理,APP设置等许多强大的功能。
你可以用它来分享你的生活动态,向其他人展示你的精彩生活瞬间;也可以欣赏到你的朋友或是其他陌生用户分享的卡片动态,如果你喜欢,你可以对它点赞,也可以把它分享到你的微信或QQ;你还以修改你的账户信息,你可以换一个可爱的头像,修改你的昵称,还可以修改你的密码信息,如果你喜欢,你还可以创建多个账户来使用控制该APP;
相信在你繁忙的工作生活中,使用PiliPili,一定会成为你最好的娱乐方式之一。
选定任务列表:
l 基于Android的图片分享软件。用户可将图片分享至平台以供其他用户浏览,用户可对喜欢的图片进行点赞保存分享等操作。
关键词:Android,图片分享,PiliPili,GitHub,Bmob
https://github.com/z1603133769/Android-PictureSharingApplication
https://gitee.com/mycyy1/Android-PictureSharingApplication
一、 前言随着互联网的普及和新兴技术的不断创新,不知不觉间手机APP成为了居民日常生活中必不可缺的一部分,人们对于APP的实用性、娱乐性、美观度等需求日益提高; 为满足这些需求,本次安卓开发的课程设计的项目就是一个关于 智能图片分享的应用——PiliPili;它以卡片的显示,展示了你的图片和想说的话,可以满足你的几乎所有应用需求:账户注册、登录,查看首页板块,点赞,分享,发布你自己的卡片(图片)信息,用户管理,APP设置等许多强大的功能。 你可以用它来分享你的生活动态,向其他人展示你的精彩生活瞬间; 也可以欣赏到你的朋友或是其他陌生用户分享的卡片动态,如果你喜欢,你可以对它点赞,也可以把它分享到你的微信或QQ; 你可以修改你的账户信息,你可以换一个可爱的头像,修改你的昵称,还可以修改你的密码信息,如果你喜欢,你还可以创建多个账户来使用控制该APP; 如果你还有疑问,你也可以在APP的设置中,点击“帮助与反馈”来查看常见问题; 为了能让用户能够在繁忙的生活工作中能够放松心情,分享动态,我们开发者会尽全力满足需求,相信该APP一定不会让您失望。
|
二、概要设计 |
需求分析 业务需求用户通过点赞、转发、上传动态等功能对喜欢的图片进行保存和分享,并存储在个人账号中 用户需求整个系统划分为注册、登录、首页、发布、“我的”五个模块,允许用户注册并登录属于自己的账号并对喜欢的图片进行点赞、转发或发表自己喜欢的图片及内容 系统需求PiliPili的功能需求 注册登录:其功能是允许用户注册个人账号,并通过个人账号进行软件的登录 首页:其功能是展示所有用户发表的图片和动态,并允许用户进行点赞、评论、转发 发布:其功能是允许用户发表图片和动态 “我的”:其功能是对个人信息进行展示,并提供用户修改密码、切换账号、退出登录、切换头像等功能,并允许用户查看APP详情,并提供基本的问题与反馈功能
整体开发设计架构
三、详细设计1、调用关系
l 打开APP,首先开始活动——登录注册,在该页面,完成新建账户,和登录操作; l 当登录成功后,进入APP首页,APP的界面是由一个RecyclerView作为主窗口,然后在底部还有一个底部导航窗口(图片和文本构建),以此完成视图切换的功能; l 主要的APP视图分为三个:“首页”、“发布”、“我的”; 数据库和网络接口:都是采用的Bmob后端云数据库,进行数据管理和接口调用; l 关于“首页模块”,它调用Bmob数据库的数据,将图片及附属信息显示在一个卡片item上(类似新闻列表的item,但是是以卡片的形式显示),然后通过自建的适配器,把一个个的item显示在首页的RecyclerView中,完成信息的显示; l 关于“发布模块”,由 EditText、ImageView、Button构成,输入文本信息和上传图片数据后,调用Bmob的文件上传接口,把数据上传到Bmob服务器中,完成上传功能; l 关于“我的模块”,调用Bmob数据库中的昵称、头像等信息进行展示,同时在‘个人空间’中调用数据库及相关静态常量的值来获取该用户点赞、发布及转发等详细信息。同时完成了修改密码、退出登录等功能 2、功能实现 2.1、登录注册模块 2.1.1 登录模块界面设计 整体框架采用线性布局,使所有组件按垂直方向进行排列,并通过相对布局来调整组件之间的相对位置,最后通过引入组件元素实现页面设计,实现布局如下:
2.1.2 登录模块逻辑设计 此处定义LoginActivity作为登录界面的活动,该界面作为App加载的第一个界面,因此在此处先开启了Bomb数据库连接和初始化工作(代码见附录)。 创建Activity完毕后,调用setContentView方法完成界面显示,并使用findViewById方法定位对应的组件,随后对这些组件加上相应的监听事件。 具体实现功能如下:①通过输入已有账户的用户名和密码后点击LOGIN按钮即可登录②密码输入框最右方的图标可以选择是否显示密码③下方的“Remember Password”选择后即可保存此处输入的密码④在输入账号密码时点击空白区域即可收起软键盘⑤点击“Sign up”后可进入注册界面。 对应功能具体实现方法如下(代码见附录):①通过定义参数获取到用户输入的账号和密码,并将其与数据库中已有账户进行匹配,若成功匹配则携带该用户的信息跳转到主页中,若匹配失败则显示相应的错误信息。②定义一个布尔值,用户每次点击该按钮即对布尔值进行取反,若该值为0则显示密码,为1则隐藏密码,具体实现在之前的实验中已详细学习,不再赘述。③此处采用SharedPreferences对密码进行储存 ④通过hideSoftInputFromWindow方法对软键盘进行隐藏 ⑤通过活动跳转到注册界面
2.2.1 注册模块界面设计 整体框架采用线性布局,使所有组件按垂直方向进行排列,并通过相对布局来调整组件之间的相对位置,最后通过引入组件元素实现页面设计,实现布局如下:
2.2.2 注册模块逻辑设计 此处定义RegiserActivity作为注册界面的活动,创建Activity完毕后,调用setContentView方法完成界面显示,并使用findViewById方法定位对应的组件,随后对这些组件加上相应的监听事件。 具体实现功能如下:通过输入合法的账号、昵称、密码并上传头像后点击注册按钮即可注册新用户并返回登录界面,点击取消按钮则直接返回登录界面,输入信息的时候点击空白处可收起软键盘 对应功能具体实现方法如下(代码见附录):对各个输入框进行监听,若输入信息完整且两次输入密码相同即可注册成功,将数据存入Bomb数据库,并通过活动跳转返回到登录界面。若点击取消则通过活动跳转直接回到登录界面
2.2、主界面设计 该APP的整体页面由以下两个部分构成 l 主视图(RecyclerView),用于显示当前页面内容信息 l 底部导航栏(TextView + ImageView),用于切换窗口视图
2.2.1 主视图(RecyclerView) 这里就是简单的创建三个xml布局文件(用于显示视图)和三个Java文件(用于控制功能和逻辑)就可以了; 具体视图如下: l Home,首页 l Add,发布页 l My,“我的“信息页 PS:根据功能需求(后面显示item,要用到网格布局),这里使用的视图并不是Fragment,而是操作和功能类似的RecyclerView;
其中针对不同的视图,根据其需求,其组件与布局,功能和逻辑也不同,关于其具体的描述,会在其它的模块中进行说明;
2.2.2底部导航栏 底部导航栏由三部分构成 l “首页”,显示图片数据信息 l “发布”,发布图片和描述信息至Bmob数据库 l “我的”,查看当前用户和APP信息与设置
其中每部分都是由一个RelativeLayout,包含着一个TextView与ImageView(显示两张图片,默认为初始图片,当前显示页面为按键页面时,替换图片和文本的显示效果和颜色)来显示,及上方显示图片,下面显示文字,最上方有一个View作为分割线,最终效果图如下
然后在MainActivity中,完成点击切换视图和图片更换操作,首先定义一个方法setSelectStatus,方法里用参数index来判断当前选择的按钮,当符合条件时,进行如下操作 l 播放按钮动画(方法为initButton) l 替换图片为激活状态 (例如:bottom_bar_image_3.setImageResource(R.drawable.my_a)) l 切换文本的颜色 (例如:bottom_bar_text_3.setTextColor(getResources().getColor(R.color.textActiveColor)) )
然后对每一个Button设置监听事件,利用switch语句,通过点击组件的id进行判断,当点击的组件为三个Button之一时,调用setSelectStatus()方法,激活按键动画并切换图片与文字,然后通过getSupportFragmentManager切换Fragment视图(因项目需求,这里用的视图不是Fragment,是RecyclerView,但方法是一样的),以此达到点击底部导航栏,切换视图的效果,例如首页按钮:
(PS:这里需要注意,当首次进入APP时,因为没有点击任何按钮,所以默认视图为空,所以这里还额外添加了一个方法setMain(),当第一次进入APP时,默认激活“首页”视图) 详细代码见 附录 2.2.2 底部导航栏-Button切换方法,
2.3、“首页”模块 2.3.1 各个图片卡片的显示 “首页”模块由两个xml文件组成 l Fragment_home 这个是一个RecyclerView视图,覆盖整个页面,然后根据网格布局的形式,显示所有的图片卡片(包含图片、描述、用户信息、点赞数等) l Card_item 这个是一个卡片item,用于显示以下几个组件 ü 图片(RoundedImageView) ü 图片描述(TextView) ü 用户头像(RoundedImageView) ü 用户名称(TextView) ü 点赞数(TextView) ü 点赞图标(ImageView) ü 分享图标(ImageView) ü 卡片背景(带阴影边框的圆角、白色背景,xml文件)
其中卡片背景是就是在drawable中创建一个显示背景的配置xml文件(设置背景的颜色,边框,圆角弧度等),然后在card_item中的RelativeLayout(所有组件的父物体)中的android:background下进行配置即可;
Card_item的显示如下:
然后就是新建CardAdapter适配器,把card_item显示在RecyclerView下(和新闻列表类似),但是和fragment不同的是,这里因为用的是RecyclerVie,所以CardAdapter需要继承RecyclerView.Adapter类,并把泛型指定为CardAdapter.ViewHolder; 然后就是新建Card(卡片类,根据组件创建需要的变量,如图片、用户名等..),然后在CardAdapter中实现静态类ViewHolder(用于组件数据的加载,变量为card_item的各组件),并重写onCreateViewHolder和onBindViewHolder这两个方法,用于加载数据到RecyclerView中,已达到显示card到主页面的效果;
接下来就需要在Fragment_home(主页面视图)中进行CardAdapter的配置了,new一个网格布局(这里需求是2列,所以参数为2),然后针对RecyclerView,设置布局管理器和适配器CardAdapter即可完成绑定操作; 核心代码如下:
详细代码请见附录2.3.1 CardAdapter的绑定与Bmob获取数据
l 关于图片数据的获取 最后是关于图片数据的获取,这里选择的数据来源是来自Bmob云数据库,它是一个Web形式的数据库,我们可以提前把数据上传到该数据库中,然后调用Bmob提供的接口(可以参考官网的接口文档),来进行数据的增删查改等常用操作;
这里我已经提前上传好了一些数据,如下:
然后调用Bmob的API接口即可获取到数据并显示在card_item中。 详细代码请见附录2.3.1 CardAdapter的绑定与Bmob获取数据
实现效果如下:
2.3.2 点击显示大图并保存图片 该功能的实现原理其实就是对图片设定一个点击事件,然后显示一个Dialog对话框,这个对话框只由目标图片构成,然后长按该图片时,会弹出“保存图片”的选择框,然后保存图片至默认路径(自己设置,不同机型可能不同),点击其它区域会销毁该对话框;
首先的设定点击事件,这里选择在CardAdapter下的onCreateViewHolder方法中设定,根据点击的位置positon,可以获取到该图片的url、描述等信息,然后通过一个方法initNetWorkImage加载图片; 这里要特别说明一下,initNetWorkImage的方法的功能是传递一个url进去,然后它会通过Glide的一个异步加载操作,根据图片的url获取到目标图片的bitmap,这样就成功获取到了图片,然后当图片获取完成时,即onPostExecute发生时,发送一个myHandler (handler 用于线程间的通信)的消息,然后把图片绑定在Dialog的ImageView下;
核心代码如下:
PS:这里之所以采用这种形式来获取图片,是因为如果直接显示card_item下的图片的话,由于card大小的问题,显示的图片的大小和尺寸都不符合要求,因此只能重新加载图片
然后就是显示Dialog,其实就是在mContxt下新建一个Dialog,然后把mImageView设置在Dialog下,最后设置一个点击销毁Dialog,长按保存图片的监听事件即可;
核心代码如下:
实现效果如下:
2.3.3 点赞功能的实现 关于点赞功能的实现,其实是使用了Bmob的一个特殊属性——Relation(关系),也可以理解为指针,它代表多对多的关系(调用Bmob的官方API接口即可使用),Bmob中新建为likes列表; 即一个卡片可以被多个用户点赞,一个用户可以点赞多个用户; Bmob数据库如下:
点赞的具体逻辑如下: l 当绑定每一个Card时,查询该卡片的like个数,likes.size(),即所有点赞该卡片的用户个数,设置该个数为该卡片的点赞数 l 然后针对likes进行查询,在likes的所有用户中查找是否包含当前已登录的用户,若已包含,则说明该卡片在之前已被当前用户点赞过了,因此设置点赞图标为激活状态,否则设置为未激活状态(本质就是更换点赞图标的图标),然后根据key-value的格式,把当前卡片的描述和卡片的点赞状态设置为一个Map存储起来,用于之后的使用; l 然后针对卡片的点赞图标设置点击监听事件,当点击点赞图标时,读取map中该卡片对应的value值,来判断是否已点赞,这时就分为两种情况; l 若已点赞,则点击时,设置点赞图标为未点赞状态,并使用Bmob的API接口,把likes中的当前用户删除(点赞个数减一); l 若未点赞,则点击时,设置点赞图标为点赞状态,并使用Bmob的API接口,在likes中添加当前用户消息(点赞个数加一);
大致的逻辑及流程如上; 针对点赞的点击事件代码如下:
其它详细代码,请见附录2.3.3 在lies中查询、添加和删除当前用户 实现效果如下:
2.3.4 分享功能的实现 逻辑如下: l 针对分享图标设置点击事件 l 当点击分享图标时,首先根据点击positon获取到点赞的卡片信息,然后把图片url作为参数传入方法initNetWorkImage(通过Glide框架,根据图片url异步获取到图片bitmap),然后获取完成后发送handle消息; l 在myHandler接受到该消息,启动方法shareImage(用于分享图片) l ShareImage,则是通过intent的方式,设置传输的类型与图片的地址,然后通过intent启动活动,自动查找手机里所有可以使用该intent的活动(例如QQ,微信等),并在分享列表shares中添加当前用户的信息; PS:此外,针对分享功能,还额外添加了一个分享列表shares,它的实现与功能与点赞列表likes几乎是完全一样,再次不在赘述;
ShareImage代码如下:
其它详细代码,请见附录2.3.4 myhandle与分享活动 实现效果如下:
2.4、“发布”模块 2.4.1界面布局 布局的话很简单,由以下三个部分组成 l EditText 填写图片描述 l ImageView 选择图片或拍照 l Button 点击后,上传图片至Bmob服务器
界面如下:
2.4.2 功能实现 首先要解决的就是选择图片的问题,根据需求,这里需求有两种途径选择图片: 拍照和相册;
具体逻辑和思想如下: l 针对ImageView做一个点击事件 l 当点击ImageView时,弹出一个Dialog选择框,选择相册或是拍照 l 然后针对选择进行不同的操作: ü 当选择相册时,首先申请对应权限,如果已申请过则直接调用图片选择方法getImageFromAlbum ü 在getImageFromAlbum中,通过intent启动相册活动 ü 然后在onActivityResult方法中,获取到相册活动的响应,调用startPhotoZoom方法裁剪图片 ü 在onActivityResult方法中,获取到裁剪活动的响应,将裁剪后的图片uri转换外bitmap,然后显示在ImageView中 ² 当选择相册时,首先申请对应权限,如果已申请过则直接调用图片选择方法getImageFromAlbum ² 在getImageFromCamera,通过intent启动拍照活动 ² 取消FileProvider的严格模式 ² 然后把拍照好的图片保存到默认路径下 ² 剩下的操作就和相册大同小异了,即在onActivityResult中,获取到拍照结果的响应,调用裁剪方法startPhotoZoom,最后获取到裁剪活动的响应,然后把uri转换为bitmap,然后显示在ImagView中; 最后就是点击Button,设置点击事件,调用Bmob的文件上传接口API,把ImageView的图片信息,EditText的图片描述文本,还有当前用户的头像、昵称等信息一并上传到Bmob服务器中(关于文件上传Bmob官方接口文档有详细说明)即可; PS:使用Bmob上传文件(图片)时,需要在Bmob官网绑定一个已注册的域名,不然不能成功上传文件; 核心代码如下:
此外,由于上传卡片信息时,根据图片的大小不同,上传的时间也不同,可能会出现等待时间较长的情况,这时我选择使用了一个GitHub的安卓框架——AVLoadingIndicatorView,它是一个等待加载的动画效果,只需要在需要显示加载动画的地方调用它对应的公开方法即可显示,然后调用关闭方法即可关闭,具体的说明文档我放在了参考文献那里;
详细代码请参考,附录2.4.2 上传卡片信息到Bmob服务器; 实现效果如下(图1-4分别为:选择对话框,成功选择图片,正在上传,上传成功):
2.5、“我的”模块 2.5.1 “我的”模块界面设计 整体框架采用线性布局,使所有组件按垂直方向进行排列,并通过相对布局来调整组件之间的相对位置,最后通过引入组件元素实现页面设计,实现布局如下:
“我的” “个人空间”
“账号与安全” “帮助与反馈”
“设置” “关于我们”
2.5.2 “我的”模块逻辑设计 此处定义LoginFragment作为“我的”界面的Fragment,使用OnCreate()方 法显示界面,使用rootView.findViewById()方法定位对应的组件,并为其添加相应监听。 具体实现功能如下: ① 登录后显示登录用户的头像、昵称及账号 ② 在“个人空间”模块可查看该用户点赞、发表、转发的记录 ③ “账号与安全”中可以更改密码 ④ “关于我们”中可查看关于该软件的详细信息 ⑤ “帮助与反馈”可查看关于该APP的常见问题 ⑥ “设置”中可以查看个人资料以及进行切换账号和退出账号等操作
对应功能具体实现方法如下(代码见附录): ①在onCreateView()中首先使用Bomb数据库的getCurrentUser方法获取当前登录用户,并通过get方法获取该用户的头像、昵称和账号等信息并转为text类型显示在相应组件上 ②点赞和转发模块——首先定义两个静态常量作为标识符,并在“主页”中对应的点赞和转发按钮上添加监听,若点赞状态为“已点赞”,则将静态常量的值设为1,通过判断该值与数据库中的标志位,即可返回对应 ③通过调取Bmob数据库中该用户的密码,与该用户输入的旧密码进行匹配,若匹配成功且其余信息无误则修改数据库后端密码并返回登录界面 ④通过Intent(getActivity(),Aboutus.class)方法切换到“关于我们” ⑤通过Itent(getActivity(),Help.class)方法切换到“帮助与反馈 ” ⑥切换界面方法同上,该界面中切换账号以及退出登录功能即跳转活动到登录界面,并写对应方法防止返回键返回上级页面
|
四、软件测试功能测试根据上面的需求分析,我们小组决定把该系统共分为几个场景模块 l 注册、登录 l 首页 l 发布 l “我的”
测试如下: 注册页面测试输入信息完整并选择合适图片作为头像即可完成注册,并返回“注册成功”的提示。 当有部分信息没输入时会提示“请填写以上信息”、头像未上传成功则会提示“请上传头像!”,若创建的用户已存在也会给出“该用户已存在”的提示。 此处存在的不足是没有对用户设置的密码做安全性检测,不论用户输入多简单的密码都能成功注册,后期将会完善这个不足 登录页面测试输入已存在的账号和正确的密码则会返回“登陆成功”的提示,并进入到首页中。若账号不存在或密码错误,都会弹出相对应的提示。“记住密码”及“显示密码”功能正常 首页页面测试登陆成功即可跳转首页,首页加载了所有用户发表的动态,屏幕上下滑动时显示正常,“点赞”和“转发”功能正常
发布页面经测试,上传图片、从本地获取图片及裁剪图片功能完善,发布功能正常,能够成功地将用户动态发布在首页中
“我的”页面经测试,个人信息显示无误,能够正确显示对应用户的昵称、账号和头像,“个人空间”模块显示无误,可以成功显示出该用户所点赞、转发和发布的动态。“账号与安全”中的修改密码功能正常,输入已登录的账号和当前密码以及新密码后即可修改密码并返回登录界面,在输入信息不完整的时候会给出相应提示,输入账号错误或密码错误也会给出相应提示。“关于我们”界面显示正确,“设置”中的切换账号和退出登录功能无误 存在的缺陷:“个人空间”模块会出现显示信息不全的情况,原因是我们在代码中加载后台数据是根据当前页面情况动态加载的,导致如果首页数据没有加载完全,显示出的“点赞”“转发”信息也不能显示完全。
兼容性测试经测试,该APP可以在不同型号的安卓手机以及各种模拟器上正常使用,说明该APP已经达成了初步的兼容性需求; 性能测试经测试,各组件间的跳转,刷新页面等操作的响应时间与数据的吞吐量均符合正常的用户需求; 对于并发操作,当一个用户在发表动态的同时,另一个用户的页面可以实时的获取到最新数据;
五、心得体会1、在进行开发时,注意不管是通过url获取图片数据,还是通过接口API获取返回数据,这些网络请求都要放在子线程中进行; 2、在进行多线程操作时,如果需要在主线程中监听子线程的进度,可以在子线程的对应位置设置一个消息发送(handleMessage),主线程只需要在myHandle中进行监听即可; 3、使用Bmob上传文件时,需要提前在Bmob官网绑定一个已注册的域名,才能使用该功能; 4、在使用RecyclerView进行布局时,记得要先设置layoutManager(可以选择布局方式,例如网格布局、瀑布布局等),然后根据需求设置Adapter; 5、当使用网络请求、存储管理等操作时,一定记得要提前注册对应权限,并在使用前弹出权限申请窗口,然后继续判断与检测; 6、使用Bmob时,记得要提前先继续用户绑定与Bmob初始化操作; 7、在布局xml中,要注意不同机型(不同尺寸)的适配问题; 8、可以通过设定Intent的类型,根据需求传递参数数据,以达到启动其它应用的功能效果; 9、在为RecyclerView新增Adapter时,要实现静态类ViewHolder(用于组件数据的加载,变量为card_item的各组件),并重写onCreateViewHolder和onBindViewHolder这两个方法,用于加载数据到RecyclerView中; 10、在使用Fragment组件时,需要将其嵌入到Activity中使用,学会了Fragment与Fragment之间的切换以及Fragemtn与Activity之间的切换; 11、从一个activity离开后,按返回键不要再回去(如从登陆界面跳转后,不要再回到登陆界面),那么我们需要把这个activity从栈区中去除 六、参考文献[1] 李刚. 疯狂Android讲义(第三版)[M]. 北京: 电子工业出版社, 2015. [2] 郭霖. 第一行代码Android(第二版)[M]. 北京:人民邮电出版社, 2017. [3] Bmob官方. Bmob Android接口文档中心[EB/OL]. http://doc.bmob.cn/data/ android/index.html,2017. [4] AVLoadingIndicatorView 框架Git文档.Git官方地址[EB/OL]. https://github.com/HarlonWang/AVLoadingIndicatorView,2016. [5] RecyclerView 控件讲解. 简书博客地址[EB/OL]. https://www.jianshu.com/p/b4bb52cdbeb7,2019.
|
今天的文章【Android】图片分享应用——PliPli分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/57360.html