注:可能学校的教务系统已经做了升级,当前的程序不知道还能不能成功获取信息,加上已经毕业,我的账户已经被注销,试不了,在这里做下思路跟过程的记录。
在我的毕业设计中”基于SSM框架贺州学院校园二手交易平台设计与实现”我有这样一个设想:使用学校教务系统账号进行贺州学院学生身份认证(通过HttpClient模拟登陆),发布者身份信息真实、平台由学生(可以跟计算机协会合作,由他们进行维护)维护,平台安全可靠,校园身份认证时本校园二手交易平台的一大特色。为了实现这个功能,我对我们学校的教务系统进行了模拟登陆。
我通过HttpClients模拟登陆教务系统,获取学生信息,使用jsoup俗称“大杀器”进行解析响应回来的html 匹配个人信息a标签地址并做携带参数页Referer进行第二次请求,使用jsoup来解析响应回来的htm匹配所有学生信息获取我们想要的学生信息。在存储过程中要进行唯一性认证,一个账号只能认证一次,一个学生教务教务系统账号只能绑定一个平台账号。
目前头像的上传我是这样做的,先把图片下载的用户电脑本地作为临时文件,再调用FtpUtil.upload()方法读取文件上传到我们nginx图片服务器,成功上传后删除用户电脑中的临时文件。(因为上传需要传入一个InputStream但是在写代码过程中发现从响应回来的HttpResponse获取到的数据转为InputStream时文件出现损失导致上传后图片无法正常打开的情况)。而一个重要的技术点就是验证码的问题,在编写代码时发现想使用Tesseract-OCR开源工具,然而,实现起来没那么简单,所以我的做法是把教务系统的验证码直接writeTo到用户的HttpServletResponse获取图片验证码,直接响应回浏览器,让用户自己手动输入再传到后台。
引入jar:(如果想在控制台打印更多连接时的信息,可以使用log4j),或者使用maven引入也行
public class AuthenticationUtil {
private static HttpClient client=HttpClients.createDefault();//实例化httpclient
//static HttpResponse response=null;
private static String rawHtml; //响应回来的数据
/* public static void main(String[] args) throws Exception {
//模拟登陆教务系统,获取学生信息
System.out.println("======模拟登陆教务系统,获取学生信息======");
//获取验证码
int i=getVerifyingCode();
if(i==1){
//提醒用户并输入验证码
System.out.println("验证码图片下载成功! D:/verifyCode.gif,请输入图片验证码:");
String code;
Scanner in=new Scanner(System.in);
code=in.nextLine();
in.close();
Map map=login("xxx","***",code);
if("200".equals(map.get("code"))){
String xm=(String) map.get("xm");//姓名
String xh=(String) map.get("xh");//学号
String lbl_xb=(String) map.get("lbl_xb");//性别
String lbl_csrq=(String) map.get("lbl_csrq");//出生日期
String lbl_sfzh=(String) map.get("lbl_sfzh");//身份证号
String lbl_xy=(String) map.get("lbl_xy");//学院
String lbl_zymc=(String) map.get("lbl_zymc");//专业
String lbl_xzb=(String) map.get("lbl_xzb");//班级
System.out.println(xm);
}else{
System.out.println(map.get("msg"));
}
}else{
System.out.println("验证码图片下载失败...");
}
}*/
/**
* 模拟登陆
* @param login_xh
* @param login_mm
* @param code
* @throws IOException
* @throws ParseException
*/
public static Map<String,String> login(String login_xh,String login_mm,String code) throws Exception {
Map<String,String> map=new HashMap<String,String>();
String __VIEWSTATE="";
HttpGet getVerifyCode=new HttpGet("http://jwxt.hzu.gx.cn/(ffxibsu12r31de551s3q4w3o)/default2.aspx");
HttpResponse response=client.execute(getVerifyCode);//获取验证码
rawHtml=EntityUtils.toString(response.getEntity(), "utf-8");
Document doc=Jsoup.parse(rawHtml);
Elements select_input=doc.select("input");
for (Element a : select_input) {
String name=a.attr("name");
if("__VIEWSTATE".equals(name)){
__VIEWSTATE=a.attr("value");
break;
}
}
//设定post参数
ArrayList<NameValuePair> postData=new ArrayList<NameValuePair>();
postData.add(new BasicNameValuePair("Button1", ""));
postData.add(new BasicNameValuePair("RadioButtonList1", "学生"));//登陆账号类型
postData.add(new BasicNameValuePair("TextBox2", login_mm));//密码
postData.add(new BasicNameValuePair("Textbox1", ""));
postData.add(new BasicNameValuePair("__VIEWSTATE", __VIEWSTATE));
postData.add(new BasicNameValuePair("hidPdrs", ""));
postData.add(new BasicNameValuePair("hidsc", ""));
postData.add(new BasicNameValuePair("lbLanguage", ""));
postData.add(new BasicNameValuePair("txtSecretCode", code));//验证码
postData.add(new BasicNameValuePair("txtUserName", login_xh));//学号
//登录 post请求
HttpPost post=new HttpPost("http://jwxt.hzu.gx.cn/(ffxibsu12r31de551s3q4w3o)/default2.aspx");//构建post对象
post.setEntity(new UrlEncodedFormEntity(postData));//捆绑参数
post.setHeader("Accept", "text/html,application/xhtml+xm…plication/xml;q=0.9,*/*;q=0.8");//带上参数页
post.setHeader("Cookie", "safedog-flow-item=");//带上参数页
post.setHeader("Connection", "keep-alive");//带上参数页
post.setHeader("Content-Type", "application/x-www-form-urlencoded");//带上参数页
post.setHeader("Host", "jwxt.hzu.gx.cn");//带上参数页
post.setHeader("Origin", "http://jwxt.hzu.gx.cn");//带上参数页
post.setHeader("Upgrade-Insecure-Requests", "1");//带上参数页
post.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36");//带上参数页
post.setHeader("Referer", "http://jwxt.hzu.gx.cn/(ffxibsu12r31de551s3q4w3o)/default2.aspx");//带上referer 参数页
response=client.execute(post);//执行登陆行为
//登录成功,发生302重定向
if(302==response.getStatusLine().getStatusCode()){
//把响应结果打印出来
System.out.println(EntityUtils.toString(response.getEntity(), "utf-8"));
Header header=response.getFirstHeader("location"); // 跳转的目标地址是在 HTTP-HEAD 中的
String newuri=header.getValue(); // 这就是跳转后的地址,再向这个地址发出新申请,以便得到跳转后的信息是啥。
newuri="http://jwxt.hzu.gx.cn/"+newuri;
// System.out.println(newuri);
//登录成功 get请求
HttpGet get=new HttpGet(newuri);
get.setHeader("Cookie", "safedog-flow-item=");//带上参数页
get.setHeader("Host", "jwxt.hzu.gx.cn");//带上参数页
get.setHeader("Referer", "http://jwxt.hzu.gx.cn/(ffxibsu12r31de551s3q4w3o)/default2.aspx");//带上referer 参数页
// client.getParams().setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 2000);
// client.getParams().setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 2000);
// 超时设置
RequestConfig requestConfig=RequestConfig.custom().setSocketTimeout(2000).setConnectTimeout(2000).build();//设置请求和传输超时时间
get.setConfig(requestConfig);
/*
* 第二次身份认证时,程序会卡在这里
*
*/
response=(CloseableHttpResponse) client.execute(get);//执行重定向
//打印输出
// rawHtml=EntityUtils.toString(response.getEntity(), "utf-8");
// System.out.println(rawHtml);
String xsgrxxUrl="http://jwxt.hzu.gx.cn/(ffxibsu12r31de551s3q4w3o)/";//个人信息a标签地址
if(200==response.getStatusLine().getStatusCode()){
//使用jsoup来解析响应回来的html 匹配个人信息a标签地址
rawHtml=EntityUtils.toString(response.getEntity(), "utf-8");
Document document=Jsoup.parse(rawHtml);
Elements select_a=document.select("a");
for (Element a : select_a) {
String href=a.attr("href");
if(href.indexOf("xsgrxx.aspx")!=-1){
xsgrxxUrl=xsgrxxUrl+href;
break;
}
}
//查看个人信息 get请求
get=new HttpGet(xsgrxxUrl);
post.setHeader("Cookie", "safedog-flow-item=");//带上参数页
post.setHeader("Host", "jwxt.hzu.gx.cn");//带上参数页
get.setHeader("Referer", newuri);//带上referer 参数页
response=(CloseableHttpResponse) client.execute(get);//执行
}
rawHtml=EntityUtils.toString(response.getEntity(), "utf-8");
//打印输出
// System.out.println(rawHtml);
//使用jsoup来解析响应回来的html 匹配所有学生信息
Document document=Jsoup.parse(rawHtml);
Elements select_span=document.select("span");
for (Element span : select_span) {
String id=span.attr("id");
if("xh".equals(id)){//学号
String xh=span.text();
map.put("xh", xh);
// System.out.println("学号:"+xh);
}
if("xm".equals(id)){//姓名
String xm=span.text();
map.put("xm", xm);
// System.out.println("姓名:"+xm);
}
// if("lbl_TELNUMBER".equals(id)){//手机号码
// String lbl_TELNUMBER=span.text();
// System.out.println("手机号码:"+lbl_TELNUMBER);
// }
if("lbl_xb".equals(id)){//性别
String lbl_xb=span.text();
map.put("lbl_xb", lbl_xb);
// System.out.println("性别:"+lbl_xb);
}
if("lbl_csrq".equals(id)){//出生日期
String lbl_csrq=span.text();
map.put("lbl_csrq", lbl_csrq);
// System.out.println("出生日期:"+lbl_csrq);
}
if("lbl_byzx".equals(id)){//毕业中学
String lbl_byzx=span.text();
map.put("lbl_byzx", lbl_byzx);
// System.out.println("毕业中学:"+lbl_byzx);
}
if("lbl_mz".equals(id)){//民族
String lbl_mz=span.text();
map.put("lbl_mz", lbl_mz);
// System.out.println("民族:"+lbl_mz);
}
if("lbl_sfzh".equals(id)){//身份证号
String lbl_sfzh=span.text();
map.put("lbl_sfzh", lbl_sfzh);
// System.out.println("身份证号:"+lbl_sfzh);
}
if("lbl_xy".equals(id)){//学院
String lbl_xy=span.text();
map.put("lbl_xy", lbl_xy);
// System.out.println("学院:"+lbl_xy);
}
if("lbl_zymc".equals(id)){//专业
String lbl_zymc=span.text();
map.put("lbl_zymc", lbl_zymc);
// System.out.println("专业:"+lbl_zymc);
}
if("lbl_xzb".equals(id)){//班级
String lbl_xzb=span.text();
map.put("lbl_xzb", lbl_xzb);
// System.out.println("班级:"+lbl_xzb);
}
if("lbl_lys".equals(id)){//所在省份
String lbl_lys=span.text();
map.put("lbl_lys", lbl_lys);
// System.out.println("所在省份:"+lbl_lys);
}
if("jtdz".equals(id)){//家庭住址
String jtdz=span.text();
map.put("jtdz", jtdz);
// System.out.println("家庭住址:"+jtdz);
}
}
//上传头像到图片服务器上
Elements select_img=document.select("img");
String imagUrl=select_img.attr("src");
getPortrait((String)map.get("xh"),"http://jwxt.hzu.gx.cn/(ffxibsu12r31de551s3q4w3o)/"+imagUrl, xsgrxxUrl);
//ftp上传到图片服务器
FileInputStream fileInputStream=new FileInputStream(new File("D:/portrait.jpg"));
CpshResult result=FtpUtil.upload((String)map.get("xh")+"_portrait.jpg", fileInputStream);
if(result.getStatus()==200){
map.put("tx", result.getData()+"");
//把文件删掉
File file=new File("D:/portrait.jpg");
file.delete();
}else{
System.out.println("头像上传失败!");
}
//成功
map.put("code", "200");
map.put("msg", "验证成功");
}else if(200==response.getStatusLine().getStatusCode()){//响应状态200,登录失败
//使用jsoup来解析响应回来的html 解析弹窗提示信息
rawHtml=EntityUtils.toString(response.getEntity(), "utf-8");
Document document=Jsoup.parse(rawHtml);
Elements select_script=document.select("script");
String msg="";
for (Element script : select_script) {
String html=script.html();
if(html.indexOf("alert")!=-1){
//只保留中文汉字
msg=html.replaceAll("[^\u4E00-\u9FA5]", "");
break;
}
}
//成功
map.put("code", "400");
map.put("msg", msg);
// System.out.println(msg);
}else if(500==response.getStatusLine().getStatusCode()){//
map.put("code", "500");
map.put("msg", "教务系统响应500...");
}
// client.getConnectionManager().shutdown();
// response.close();
return map;
}
/**
* 获取图片验证码,直接响应回浏览器
* @param client
* @return
*/
public static void getVerifyingCode(HttpServletResponse response1){
try {
HttpGet getVerifyCode=new HttpGet("http://jwxt.hzu.gx.cn/(ffxibsu12r31de551s3q4w3o)/CheckCode.aspx");//验证码get
HttpResponse response=client.execute(getVerifyCode);//获取验证码
if(200==response.getStatusLine().getStatusCode()){//200响应码
//将响应回来的图片信息writeTo 到 fileOutputStream
response.getEntity().writeTo(response1.getOutputStream());
}
} catch (Exception e) {
e.printStackTrace();
// return 0;
}finally {
}
}
/**
* 获取图片验证码,下载到本地
* @param client
* @return
*/
public static int getVerifyingCode(){
FileOutputStream fileOutputStream=null;
try {
// HttpClient client=HttpClients.createDefault();//实例化httpclient
HttpGet getVerifyCode=new HttpGet("http://jwxt.hzu.gx.cn/(ffxibsu12r31de551s3q4w3o)/CheckCode.aspx");//验证码get
HttpResponse response;
response=client.execute(getVerifyCode);//获取验证码
if(200==response.getStatusLine().getStatusCode()){//200响应码
/*验证码写入文件,保存为verifyCode.jped*/
fileOutputStream=new FileOutputStream(new File("D:/verifyCode.gif"));
//将响应回来的图片信息writeTo 到 fileOutputStream
response.getEntity().writeTo(fileOutputStream);
return 1;
}else{
return 0;
}
} catch (Exception e) {
e.printStackTrace();
return 0;
}finally {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 下载头像图片
* @param cilent
* @param imagUrl
* @param referer
* @return
*/
public static void getPortrait(String xh,String imagUrl,String referer){
// HttpClient client=HttpClients.createDefault();//实例化httpclient
FileOutputStream fileOutputStream=null;
try {
// HttpClient client=HttpClients.createDefault();//实例化httpclient
HttpGet portrait=new HttpGet(imagUrl);//get请求 头像照片
//portrait.setHeader("Referer",referer);
HttpResponse response=client.execute(portrait);// 执行
if(200==response.getStatusLine().getStatusCode()){//200响应码
// /*图片写入文件,保存为portrait.jpg*/
File file=new File("D:/portrait.jpg");
file.createNewFile();
fileOutputStream=new FileOutputStream(file);
response.getEntity().writeTo(fileOutputStream);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
fileOutputStream.flush();
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
可以再控制台打印出来,我这里是作为layer弹窗:
作者:huanzi-qch
出处:https://www.cnblogs.com/huanzi-qch
若标题中有“转载”字样,则本文版权归原作者所有。若无转载字样,本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利.
辑导语:B端产品的展现形式包含了很多类型,标签页、弹窗、悬浮层等等。本篇文章中作者分享了如何正确的呈现B端产品,让产品的交互体验更加丝滑。感兴趣的小伙伴们快来一起看看吧,希望对你有所帮助。
在B端产品操作中,需要高频率地打开各类链接和按钮,如果点击后需要展示新的内容,那么展现形式就包含了很多种类型,标签页、新页面、悬浮层、弹窗、抽屉等等。
在面对数量庞大的B端页面、组件、交互场景下,应该选择哪种展示形式就变成了一个棘手的问题。
本篇分享就将集中在解决如何选择正确的呈现形式上,让产品的交互体验更顺滑。
网页是一种可视化的UI界面,也是一种内容载体,它是浏览器访问网站后显示的主要对象,也是浏览器展示内容中层级最高的单位。
在同一个网站中,如果我们想要访问其它网页,就需要点击按钮或链接触发,这时候,打开新网页的方式就有两种,在新窗口/标签中打开(_blank)或者在本窗口/标签中打开(_self)。
不管是哪种,本质上都需要浏览器重新加载新的页面。对于一般的企业官网、新闻网站来说,这种加载的模式没有太大的问题,因为操作频次相对适中,用户中间会有比较长的时间停顿下来查看页面的内容信息。
而B端项目则不同,虽然也有不少查看页面信息的需求,但是包含了更多需要短平快完成操作目标的使用场景,比如修改个标题,更改商品价格,添加分类字段等。
如果所有高频操作的场景,都要重新加载页面,使用起来的 “顿挫感” 是非常强的,降低使用体验。
早期的网站加载内容必须刷新页面,所以顿挫感是难以解决的,只能想办法减少跳转流程来提升用户体验。
随着网页技术的发展,异步处理(AJAX数据交换方式)技术的应用,让网页的内容可以通过不刷新或加载新网页的形式加载和显示。
简单解释,就是早期的网页加载完成以后就是 “静止” 的,里面所有内容是固定的(不是HTML的静态)。而异步处理,就是让页面中的指定模块处于 “运动” 的状态,客户端可以在不重载网页的情况下只加载和更新这个模块的内容。
比如下面的案例,设置不同的条件选项,在过去的网页中只能重载页面更新,而使用异步处理就可以直接和服务器请求数据刷新这个图表模块,而不用重载整个页面。
所以,在B端项目中,我们不再是只有重载网页一个选项,而有了其它的选择,如下图所示。
其中,网页展示作为一个基础展示对象,我就不多做介绍了。我会通过分别介绍其它几个内容的载体,帮助大家区分它们和重载页面有何不同,以及如何正确选择内容加载形式。
首先介绍浮层,它是我对通过悬浮在页面基础内容之上的内容层的统称。例如各类气泡、提示框、下拉菜单,都是浮层的表现形式。
浮层是比较底层的形式,其展示内的容完全不需要使用一个新的页面,且和触发的元素有较强的视觉联系(对比弹窗)。
浮层并不是由内容的多和少决定的,复杂的浮层可以包含非常多的交互选项和内容信息,导致我们很容易和下方解释的弹窗搞混。
比如客户端软件常见的隐藏式侧边栏,搜索栏中展开的复杂面板,都是浮层的一种而不是弹窗。
浮层最大的特点,源自它的位置定义逻辑,它会和触发它的元素具有非常紧密的位置关系,而不是像弹窗一样无差别显示在界面或浏览器视图的固定区域。
如果我们想要显示内容,完全没到用一个新页面展示的地步(如搜索建议面板),且和触发它的控件有较强的联系,就可以考虑使用浮层来展示。
弹窗,也是一种悬浮在基础内容之上的内容层,它和浮层的不同之处,就在于弹窗通常是居中固定的显示区域,和触发它的元素没有什么位置联系。并且,弹窗可以包含的内容量级也是高于浮层的。基础的弹窗包含强提示弹窗,或类似注册登录这种表单弹窗。
而高级的弹窗,则类似下方案例,约等于打开一个独立的网页。
之所以使用这些高级弹窗作为页面载体,原因就是对原触发页面的使用和关注并没有结束,需要支持快速关闭当前的窗口并返回原来的页面中去。
比如在一个非常长的列表中,你下滑了几十页的高度,肯定不想放弃掉当前的页面位置,所以Behance或者花瓣等应用,都采用窗口模式加载新页面。
或者类似一个列表页面中需要大量创建新的数据,这些数据又不复杂。于是就通过弹窗表单的形式,快速完成创建并在原页面中再次点击 “新增” 按钮。
高级的弹窗使用除了保持原页面位置、高频操作等防止加载的原因之外,还有个更重要的特征,就是强制吸引用户的注意力到窗口上。
因为弹窗主要以模态 (Modal,后方有黑色遮罩)居中显示,通过深色蒙版进行前后隔断,凸显弹窗区域,意味着我们强制让用户关注眼前的信息和任务。
如果我们想要显示的内容,需要保留原页面状态,减少页面跳转数量,又需求用户强行关注,就可以使用这种模式展示。
最后,就是最难选择,也最容易和其它组件搞混的抽屉了。
抽屉本身的特征包含悬浮属性,覆盖在原页面之上。而我们常见的侧边栏、侧边菜单并不能和抽屉画上等号,因为他们会占用画布的实际显示区域,和原有内容同层。
比如下方案例中,Jira左侧导航(不分左右)可以隐藏收入,页面内容变大,这是侧边栏。而点击列表选项,右侧弹窗的窗口覆盖原有页面,才是抽屉。
和高级的弹窗类似,抽屉也可以当成一个独立的页面展示信息。但它和弹窗不同的是,抽屉通常是从页面的右侧展开,没有遮挡左侧的空间。它的主要特征是还需要在原页面进行交互。
比如Teambition案例中的列表,我们每开一个抽屉都还可以直接点击原列表的其它选项切换下一个抽屉,省掉关闭步骤或者原页面被遮挡的情况。
它比较适合应用在表格/列表环境中,作为表格/列表内容的详情页形式展开,这样用户可以在一个页面中快速查看不同列表的具体信息或编辑。并且,表格/列表本身的特征会将标题放在最左侧,也方便抽屉的切换。
也因为这种特性,抽屉不太需要使用模态和遮罩将左侧内容遮挡掉。如果需要通过遮挡来吸引用户注意力,那么这种情况往往更适合使用弹窗。
所以,如果不想通过新页面打开的列表详情内容,且不是强制要求用户聚焦的任务,就可以使用抽屉的形式展现。
以上就是几种基本的内容展现形式说明,时间关系还有后半部分关于如何站在系统框架级的角度使用内容载体的分享,我会留在下次分享。
如果有关于这部分的实际项目疑问,也可以在下方留言。
我们下篇再见~
作者:酸梅干超人;公众号:超人的电话亭
本文由 @超人的电话亭 原创发布于人人都是产品经理。未经许可,禁止转载。
题图来自 Unsplash,基于CC0协议。
一直都很信赖WPS,也很支持WPS,就是因为wps对之前word个版本之前的兼容很好。
前几天wps强制我更新了版本现在的版本:10.1.0.5850,以为是正常更新,一运行,就跳出登录窗口。因为本人的不喜欢弹窗关掉,但是提示要是不登录就要关掉整个程序。为什么关掉登录窗口,还不给未登录的用户的入口,就强制把WPS也关闭?!你们难道要彻底放弃不能上网的用户,不上网就不能用WPS?因为不能看你们的广告,不能看到你们推送的东西??!!!来看看最近我们公司客服同事对这的感受。
wps强制登陆用户体验感受
真的是很无语,今天突然有种被嫌弃、被抛弃的感觉……这让我整个联想到最近百度出来的一个算法,打击用户体验不好的网站:
强行弹窗app下载、用户登录、大面积广告等影响用户正常浏览体验的页面,尤其以必须下载app才能正常使用的站点为代表。从整个移动互联网生态环境看,越来越多的网站进行此类强推,这已经严重影响了正常用户的浏览体验。
要是百度对这种通过百度搜索进来的软件有这样的惩罚到是能够平息我现在的心情。一万个草泥马从眼前奔腾而过,其实虽然不爽,但是我现在写下这边文章的额时候还是用的第三方登陆了wps文字word编辑的。期待在一定时间里金山测试好用户登陆这个后,会不会撤销这个很让人烦的登陆功能。毕竟这个在每个应用到一定时期会需要用户注册的强制首段。PS:但是这个弹出就算了,关掉还不让用,这个小编给差评!
用户不想注册,越引导越反感。
把内容做好,注册醒目一点,不要喧宾夺主。
看到特别优秀的网站,我浏览后会主动找注册入口,为了获得更全面的体验。
大家来说说!!
分享来源: 任伟SEO博客 转载请注明出处!(QQ交流:547701130/微信:renweiseo/微信公众号:renwei_seo)
原文地址:http://www.renweiseo.com/blog/view/4330.html
*请认真填写需求信息,我们会在24小时内与您取得联系。