分类

课内:
不限
类型:
不限 毕业设计 课程设计 小学期 大作业
汇编语言 C语言 C++ JAVA C# JSP PYTHON PHP
数据结构与算法 操作系统 编译原理 数据库 计算机网络 软件工程 VC++程序设计
游戏 PC程序 APP 网站 其他
评分:
不限 10 9 8 7 6 5 4 3 2 1
年份:
不限 2018 2019

资源列表

  • 基于C++的餐厅管理程序的设计与实现

    摘 要随着计算机应用技术的快速发展和日益普及,网络也遍及到我们生活的每个角落,为我们的学习和工作带来极大的方便。很多人都使用过传统的文字,纸张管理手段,与之不同的另外一种管理方式就是利用电脑程序管理餐厅。主要对追求工作高效、稳定、便捷的餐厅管理人员,电脑程序管理餐厅是一种很好的新型管理手段,它能提高工作效率和方便使用,餐厅管理程序就涉及到了文字、数字、项目信息的输入、读取、修改以及保存。
    本程序针对真实餐厅工作状况进行分析,讨论工作平台的环境影响,比较后采用在VS开发平台下,调用文件读写函数以实现Windows系统下信息的更新和保存。通过同类型软件测试比较可得:餐厅管理系统软件的设计具有先进性,可靠性。
    关键词:餐厅管理;文件读写;数据更新、保存
    第一章 设计内容设计一个模拟实现餐厅管理系统的程序,输入你需要的操作(选择相对的括号里的阿拉伯数字)程序具体功能有:

    查看菜单
    新增菜单
    删除菜单
    修改菜单(含名称及价格)
    查看座位剩余量
    修改座位剩余量
    查看预定情况
    新增预定(含菜式预定)
    查看顾客积分
    顾客兑换积分

    第二章 总体设计2.1 模块化设计为实现系统功能,本程序主要分为八个模块。它们分别为:查看菜单、新增菜单、删除菜单、修改菜单、查看座位剩余量、修改座位剩余量、查看预定情况、新增预定。这八个函数再通过主函数调用分别得以实现。
    主函数,首先提供了程序运行时的友好界面,提供客户选择的菜单。然后,通过执行多分支选择语句——switch语句,分别实现其它各个函数的调用功能。
    其它各个函数的功能分别如下:
    check() // 查看菜单函数add() // 新增菜单函数decrease() // 删除菜式函数change() // 修改菜式函数seat_check() // 查看座位剩余量函数seat_change() // 修改座位剩余量函数booking_check() // 查看预定情况函数booking_add() // 新增预定座位函数draw_check() // 查看顾客积分draw_check2() // 查看礼品列表 draw_main() // 兑换功能
    2.2 程序运行示意图
    第三章 详细设计3.1 数据结构设计本程序中,运用了多种数据结构。首先进入了主界面,查看菜单、新增菜单、删除菜单、修改菜单、查看座位剩余量、修改座位剩余量、查看预定情况、新增预定。在主函数中分别调用各项的函数。
    对于主要数据结构的举例如下:
    //定义菜单类class menu{private: //基本元素 int num,price;//菜式序号、价格 char name[20];//菜式名称 //用于功能实现的元素 int mealnum,delnum,changenum,changeprice;//菜式总数目、删除序号、修改序号、修改菜式序号 char changename[20];//修改菜式名字public: menu(); ~menu();//析构函数 void check();//查看菜单 void add();//新增菜单 void decrease();//删除菜单 void change();//修改菜单,包括修改菜式名称和菜式价格 void change_name();//修改菜式名称 void change_price();//修改菜式价格};//座位类class seat{private: //基本元素 int num,rest;//座位序号,座位剩余量 char size[20];//座位大小 //用于功能实现的元素 int seatnum,changenum,rerest;//座位种类数目,修改序号,剩余量public: seat(int); ~seat();//析构函数 void seat_check();//座位查看 void seat_change();//座位剩余量修改};//预定类class booking:public seat{private: //基本元素 int bnum,phone,peoplenum; char bname[20],bmeal[100]; //功能实现的元素 int bookingnum,cancelnum;//预约信息总数目、取消的序号public: booking(); ~booking();//析构函数 void booking_check();//预约查看功能 void booking_add();//新增预约};//主菜单void working(){ cout<<"======餐厅管理程序======"<<endl; cout<<endl<<"====菜式项目===="<<endl<<"1.查看菜式 "<<"2.新增菜式 "<<"3.删减菜式 "<<"4.修改菜式"<<endl; cout<<endl<<"====桌位项目===="<<endl<<"5.查看桌位 "<<"6.修改桌位剩余量 "<<endl; cout<<endl<<"====预定项目===="<<endl<<"7.查看预定 "<<"8.新增预定"<<endl; cout<<endl<<"====输入0退出系统===="<<endl;}//福利类class draw{private: char dname[20];//名称 int dnum,dphone,dpoint,total,dchange,duse;//序号,手机尾号,积分数目,记录表的总人数,兑换人号码,兑换礼品的序号public: draw(); ~draw(); void draw_check();//查看积分 void draw_check2();//查看奖品 void draw_main();//使用积分};void select(){ menu A; seat B; booking C; int chiose; while(1) { system("cls"); working(); cout<<endl<<"请输入你要实现的功能的序号:"; cin>>chiose; cout<<endl; switch(chiose) { case 0: exit(0); break; case 1: A.check(); break; case 2: A.add(); break; case 3: A.decrease(); break; case 4: A.change(); break; case 5: B.seat_check(); break; case 6: B.seat_change(); break; case 7: C.booking_check(); break; case 8: C.booking_add(); break; default: cout<<"输入无效请重新输入!"<<endl; cout<<endl; break; } getchar(); getchar(); }}
    3.2 程序模块设计本程序执行的入口是main函数,在main函数中首先调用了界面类中的各项操作的函数,在界面类中选择所需要进行的操作,在switch语句进行界面显示,实现对餐厅管理系统算法的操作。其中涉及到被调用的方法有:
    void working(){ cout<<"======餐厅管理程序======"<<endl; cout<<endl<<"====菜式项目===="<<endl<<"1.查看菜式 "<<"2.新增菜式 "<<"3.删减菜式 "<<"4.修改菜式"<<endl; cout<<endl<<"====桌位项目===="<<endl<<"5.查看桌位 "<<"6.修改桌位剩余量 "<<endl; cout<<endl<<"====预定项目===="<<endl<<"7.查看预定 "<<"8.新增预定"<<endl; cout<<endl<<"====输入0退出系统===="<<endl;}void select(){ menu A; seat B; booking C; int chiose; while(1) { system("cls"); working(); cout<<endl<<"请输入你要实现的功能的序号:"; cin>>chiose; cout<<endl; switch(chiose) { case 0: exit(0); break; case 1: A.check(); break; case 2: A.add(); break; case 3: A.decrease(); break; case 4: A.change(); break; case 5: B.seat_check(); break; case 6: B.seat_change(); break; case 7: C.booking_check(); break; case 8: C.booking_add(); break; default: cout<<"输入无效请重新输入!"<<endl; cout<<endl; break; } getchar(); getchar(); }}void main(){ select(); system("pause");}
    3.3 流程图查看菜单

    新增菜单

    删除菜单

    修改菜单(价格和名称)

    查看座位剩余量

    修改座位剩余量

    查看预定信息

    新增预定

    查看积分

    福利兑换

    第四章 调试与测试4.1 调试过程中的主要问题本程序,即餐厅关系系统程序,是涉及到了有关文件的读取、修改、清除、关闭等多个不同方面的操作,其中对文本文件的读取并输出到屏幕、修改最为棘手,同时这也是本程序的核心,关键内容。在开始的时候,我并无头绪,后来在网络上参考了网友的问题和解决方案后,找到了解决问题的思路,即打开文件-保存内容到数组中-清除文件内容-关闭文件-按需求修改数组内容-打开文件(已为空)-数组内容写入文件-关闭文件。使用这思路,简单明了,易于理解,便于操作,思路清晰,解决问题的相关代码也随之而出,复杂的核心问题得到解决,其他内容也就迎刃而解。
    4.2 具体测试过程主界面

    显示菜单

    新增菜单


    删除菜单


    修改菜单(价格)


    修改菜单(价格)


    查看座位剩余量

    修改座位剩余量


    查看预定情况

    新增预定

    查看积分

    兑换功能


    参考文献[1] 陈维兴 林小茶,C++面向对象程序设计教程(第3版)。北京:清华大学出版社,2009年5月
    [2] 严蔚敏 吴伟民,数据结构(C语言版)。北京:清华大学出版社,2011年5月
    [3] Thomas H.Cormen Charles E.leiserson Ronald L.Rivest Clifford Stein, 算法导论。机械工业出版社,2010年11月
    [4] 王晓东,计算机算法设计与分析(第4版)北京:电子工业出版社,2012年2月
    2 评论 24 下载 2018-11-05 09:11:35 下载需要5点积分
  • 基于C语言实现的超市管理系统

    1 需求分析超市随着市场经济和现代信息技术的的发展,不可必要的要卷入信息现代化的大潮,如何使用现代化的工具,使企业和经营者个人在最小的投入下获取最大的回报,成为每一个人的梦想。因此,在超市管理中引进现代化的办公软件,就成为时下最好的解决办法。使用软件办公,不仅能使经营者的劳力资本减少,同时能使超市的管理更规范更合理,解决了超市中用于管理进出货以及购买、库存等众多繁琐、工作量巨大从而导致耗费人员过多,超市经营紊乱等众多问题。
    1.1 主要功能
    超级管理员模块:(需帐号登录)

    添加管理员删除管理员
    管理员模块:(需帐号登录)

    商品信息录入(编号、名称、单价、库存)商品信息修改(修改商品的各种信息)商品信息查询(根据所输入的商品名字,编码查询库存信息)商品信息删除(从商品文件中删除商品信息)商品信息排序(按编号,单价,库存3种模式进行排序)输出商品全部库存信息删除普通用户功能(管理员有权限删除普通用户帐号)
    普通用户模块:(需帐号登录)

    新用户注册用户登录:
    商品查询商品排序输出商品全部库存信息购买商品(通过用户余额来进行购买)充值(通过输入充值卡卡号对用户余额充值)修改用户密码


    1.2 运行环境
    操作系统:Windows NT/2000/XP/VISTA/WIN7/WIN8/WIN10
    开发平台:Microsoft Visual Studio 2012

    1.3 文件说明
    程序源代码(超市管理系统.cpp)
    程序运行文件(超市管理系统.exe)
    管理员信息(管理员信息.txt)
    普通用户信息(普通用户信息.txt)
    商品信息(商品信息.txt)

    2 总体方案设计2.1 设计方案整个超市管理系统分为3个模块,超级管理员+管理员+普通用户,其中:

    管理员可以实现商品信息的录入,修改,查询等管理操作,同时还拥有对普通用户帐号的管理
    超级管理员是最高执行者,拥有对管理员帐号的操作权限,可以对管理员帐号进行添加或删除
    普通用户模块,由于考虑到该程序的真实性,所以实现了普通用户的注册和登录功能,用户要进行购物,必须使用帐号(可自行注册,无须经过管理员),其中普通用户具有商品查询等一部分功能。特色功能是具有帐号余额功能,同时购物可以进行充值功能(输入充值卡卡号,类似于购买充值卡来充值)

    2.2 主要结构
    超级管理员模块:(需帐号登录)

    添加管理员删除管理员
    管理员模块:(需帐号登录)

    商品信息录入(编号、名称、单价、库存)商品信息修改(修改商品的各种信息)商品信息查询(根据所输入的商品名字,编码查询库存信息)商品信息删除(从商品文件中删除商品信息)商品信息排序(按编号,单价,库存3种模式进行排序)输出商品全部库存信息删除普通用户功能(管理员有权限删除普通用户帐号)
    普通用户模块:(需帐号登录)

    新用户注册用户登录:
    商品查询商品排序输出商品全部库存信息购买商品(通过用户余额来进行购买)充值(通过输入充值卡卡号对用户余额充值)修改用户密码


    2.3 程序功能和流程图由于本程序模块功能较多,有超级管理员,管理员和普通用户,共3个模式的功能,流程图较大,因此分为3个子图。
    超级管理员模块

    管理员模块

    普通用户模块

    2.4 数据结构和算法的设计说明本程序有3个数据结构:管理员,普通用户,商品共3个结构体。其中程序拥有超级管理员、管理员和普通用户共3种身份,运用链表来存储和读取所保存的信息。
    2.4.1 结构体类型一开始选择模块,输入帐号和密码,进行验证,是否正确。正确就进入功能模块。
    typedef struct custom* cpointer;//结点指针类型struct custom{ string custom_ID;//帐号 string custom_name;//姓名 string custom_mima;//密码 double money;//账户余额 cpointer next;};typedef cpointer clklist;//单链表类型,即头指针类型
    其中,管理员、普通用户、商品都是定义结构体,而后进入系统,一开始便定义相对应的链表进行存储和获取已保存的信息。
    2.4.2 算法设计排序算法
    for(int i=0;i<Goodnum;i++){ for(int j=Goodnum;j>=i+1;j--){ if(Go.Good[j].Good_ID<Go.Good[j-1].Good_ID) {//比较编号大小,小的在前面,大的在后面 flag=1; p=Go.Good[j]; Go.Good[j]=Go.Good[j-1]; Go.Good[j-1]=p; } }
    通过对相邻2个商品的编号不断比较,排序,运用2个for循环,比较编号大小,小的在前面,大的在后面。不断变换,最终排序完成。
    2.5 程序关键源代码说明程序中拥有3个身份:超级管理员,管理员,普通用户。不同身份之间函数都差不多,因此将函数直接展示,不说明为哪个身份的权限功能。
    custom_ender函数:登录验证函数。对输入的帐号和密码进行验证检索,正确就进入对应的身份模块。先定义一个普通用户结构体类型cpointer对象p,把包含普通用户全部信息的单链表head赋值给p,而后对p进行检索,如果p链表中的用户信息与输入的用户信息(帐号+密码)相同,就返回1;不同就显示“用户帐号不存在”,同时返回0。
    int custom_ender(clklist &head,string ID,string mima){ cpointer p; p=head; while(p!=NULL) { if(p->custom_ID==ID&&p->custom_mima==mima) return 1; else p=p->next; } cout<<"用户帐号不存在!"<<endl; return 0;}
    custom_creat函数:注册函数。先定义一个普通用户结构体类型cpointer对象p,把包含普通用户全部信息的单链表head赋值给p,而后输入注册用户的各种信息(帐号,密码,姓名),如果输入的帐号ID与已保存的用户帐号相同,就显示已存在,提示重新输入。最后将输入的新注册用户信息运用头插法插入,保存到”普通用户信息.txt”文件中保存
    void custom_creat(clklist &head){ cpointer s,p; string ID,name,mima; int sign=1,flat=1; while(sign!=0){ flat=1; cout<<"请输入用户帐号"<<endl; cin>>ID; p=head->next; while(p!=NULL){ if(p->custom_ID==ID) flat=0; p=p->next; } if(flat==0) { cout<<"用户帐号已存在,请重新输入"<<endl; continue; } cout<<"请输入用户密码"<<endl; cin>>mima; cout<<"请输入用户姓名"<<endl; cin>>name; s=new custom; s->custom_ID=ID; s->custom_name=name; s->custom_mima=mima; s->money=0; s->next=head->next;//使用头插法建表 head->next=s; buyernum++;//输入一个用户信息,buyernum自加1 custom_save(head); cout<<"是否继续注册?<继续>请按1 <结束>请按0"<<endl; cin>>sign;//while循环判断条件,所以不需要用if }}
    custom_get函数:从文件获取用户信息函数。先定义一个普通用户结构体类型cpointer对象p,把包含普通用户全部信息的单链表head赋值给p, 而后运用一个for循环,将用户信息从“普通用户信息.txt“全部导入到p链表中,返回给系统。
    clklist custom_get(clklist &buyer){ cpointer s,p;//s用于存储用户信息,p用于buyer的连接 string numname; string ID,name,mima; double money; buyer->next=NULL; p=buyer; ifstream ifile("C:\\普通用户信息.txt",ios::in); if(!ifile){ cerr<<"用户信息查询出错"<<endl;return 0; } ifile>>numname; ifile>>buyernum;//从文件中提取用户个数,用于for循环 for(int i=1;i<=buyernum;i++){ ifile>>ID; ifile>>name; ifile>>mima; ifile>>money; s=new custom; s->custom_ID=ID; s->custom_name=name; s->custom_mima=mima; s->money=money; s->next=p->next; p->next=s; } buyer=p; ifile.close(); return buyer;}
    Good_add函数:商品信息录入函数。把包含商品信息的链表Goods传入该函数,输入商品编号,if(Good_ender1(Goods,ID)==0)进行检索是否商品已存在,存在就提示重新输入,不存在就继续录入商品信息,导入到Goods链表中,最后运用Good_save(Goods)保存全部信息到“商品信息.txt”文件中。
    void Good_add(sqlist &Goods){ string ID,name; double piece; int last,sign,flat=1;//last为商品库存,sign用于判断选择 fstream ifile("C:\\商品信息.txt",ios::in); for(;flat!=0;) { cout<<"请输入商品编号:"; cin>>ID; if(Good_ender1(Goods,ID)==0) { cout<<"请输入商品名称:"; cin>>name; if(Good_ender2(Goods,name)==0) { cout<<"请输入商品单价:"; cin>>piece; cout<<"请输入商品数量:"; cin>>last; Goods.Good[Goodnum].Good_ID=ID; Goods.Good[Goodnum].Good_name=name; Goods.Good[Goodnum].piece=piece; Goods.Good[Goodnum].last=last; Goodnum++;//添加完信息就自加1 Good_save(Goods);//添加完就保存至商品信息.txt cout<<"是否继续录入?<继续>请按1 <结束>请按0"<<endl; cin>>sign; if(sign==0) break; } else {cout<<"商品名称已存在!<重新输入>请按1 <结束>请按0"<<endl; cin>>flat;} } else {cout<<"商品编号已存在!<重新输入>请按1 <结束>请按0"<<endl; cin>>flat;} }}
    Good_change函数:商品信息修改函数。把包含商品信息的链表Goods传入该函数,考虑到用户可能只记得商品编号或者商品名称,因此编写了2种修改模式(按编号检索修改,还是按名称检索修改)。如果选择按编号检索修改,同样对编号进行检索,如果在Goods链表中找到该商品编号,就运用 sign=Good_locate1(Goods,ID);定位记录下该商品在文件中的位置,而后输入修改后的商品信息,保存至文件中;按名称检索修改类似。最后修改完后,会提示是否继续修改,如果不继续就返回用户功能界面,执行其他功能。
    void Good_change(sqlist &Goods){ string ID,name; int sign;//sign用于定位要修改商品的位置 int a,flat=1;//a用于switch for(;flat!=0;){ cout<<"<输入要修改的商品编号>请按1 <输入要修改的商品名称>请按2"<<endl; cin>>a; if(a!=1&&a!=2) {cout<<"选择有误,请重新输入"<<endl;continue;} switch(a){ case 1: { cout<<"请输入商品编号:"; cin>>ID; if(Good_ender1(Goods,ID)==1) { sign=Good_locate1(Goods,ID); cout<<"商品编号:"<<Goods.Good[sign].Good_ID<<endl; cout<<"商品名称:"<<Goods.Good[sign].Good_name<<endl; cout<<"商品单价:"<<Goods.Good[sign].piece<<endl; cout<<"商品库存:"<<Goods.Good[sign].last<<endl; cout<<"请输入修改后的信息:"<<endl; cout<<"商品编号:"; cin>>Goods.Good[sign].Good_ID; cout<<"商品名称:"; cin>>Goods.Good[sign].Good_name; cout<<"商品单价:"; cin>>Goods.Good[sign].piece; cout<<"商品库存:"; cin>>Goods.Good[sign].last; Good_save(Goods);//保存信息 cout<<"修改成功"<<endl; } else cout<<"商品不存在!"<<endl; break; } case 2: 大致与编号模式相同 else cout<<"商品不存在!"<<endl; break; } }//switch的 cout<<"<继续修改>请按1 <退出>请按0"<<endl; cin>>flat; }//for循环的}
    Good_inquire函数:商品信息查询函数。把包含商品信息的链表Goods传入该函数,考虑到用户可能只记得商品编号或者商品名称,因此编写了2种查询方式(按编号检索查询,还是按名称检索查询)。如果选择按编号检索查询,同样对编号进行检索,如果在Goods链表中找到该商品编号,就运用 sign=Good_locate1(Goods,ID);定位记录下该商品在文件中的位置,而后输出该商品信息的全部信息;按名称检索查询类似。最后查询完后,会提示是否继续查询,如果不继续就返回用户功能界面,执行其他功能。
    void Good_inquire(sqlist &Goods){ string ID,name; int a,sign,flat=1; for(;flat!=0;){ cout<<"<按商品编号查询>请按1 <按商品名称查询>请按2"<<endl; cin>>a; if(a!=1&&a!=2) {cout<<"选择有误,请重新输入"<<endl;continue;} switch(a){ case 1: { cout<<"请输入商品编号:"; cin>>ID; if(Good_ender1(Goods,ID)==1) { sign=Good_locate1(Goods,ID); cout<<"商品编号:"<<Goods.Good[sign].Good_ID<<endl; cout<<"商品名称:"<<Goods.Good[sign].Good_name<<endl; cout<<"商品单价:"<<Goods.Good[sign].piece<<endl; cout<<"商品库存:"<<Goods.Good[sign].last<<endl; } else cout<<"商品不存在!"<<endl; break; } case 2: 大致与编号模式相同 else cout<<"商品不存在!"<<endl; break; } }//switch的 cout<<"是否继续查询?<继续>请按1 <结束>请按0"<<endl; cin>>flat; if(flat==0) break; }//for循环的}
    Good_delete函数:商品信息删除函数。把包含商品信息的链表Goods传入该函数,考虑到用户可能只记得商品编号或者商品名称,因此编写了2种删除方式(按编号检索删除,还是按名称检索删除)。如果选择按编号检索删除,同样对编号进行检索,如果在Goods链表中找到该商品编号,就运用 sign=Good_locate1(Goods,ID);定位记录下该商品在文件中的位置,而后使用for(int i=sign; i\<Goodnum; i++)将该位置后的商品全部前移1位,覆盖掉该位置,最后保存删除后的商品信息至“商品信息.txt”中;按名称检索删除类似。最后删除完后,会提示是否继续删除,如果不继续就返回用户功能界面,执行其他功能。
    void Good_delete(sqlist &Goods){ int a,sign,flat=1; string ID,name; for(;flat!=0;){ cout<<"<输入要删除的商品编号>请按1 <输入要删除的商品名称>请按2"<<endl; cin>>a; if(a!=1&&a!=2) {cout<<"选择有误,请重新输入"<<endl;continue;} switch(a){ case 1: { cout<<"请输入要删除的商品编号:"; cin>>ID; if(Good_ender1(Goods,ID)==1){ sign=Good_locate1(Goods,ID); for(int i=sign;i<Goodnum;i++){ Goods.Good[i]=Goods.Good[i+1]; } Goodnum--; Good_save(Goods); cout<<"删除成功!"<<endl; } else cout<<"商品不存在!"<<endl; break; } case 2: 大致与编号模式相同 else cout<<"商品不存在!"<<endl; break; } }//switch的 cout<<"是否继续删除?<继续>请按1 <结束>请按0"<<endl; cin>>sign; if(sign==0) break; }//for循环的}
    Good_range函数:商品信息排序函数。把包含商品信息的链表Go传入该函数,选择排序模式(按编号,单价,库存进行排序),运用2层for循环,使用冒泡法进行排序,从尾部向头部进行对比检索,小的放前面,大的放后面,排序完之后就将排序后的商品信息显示给用户;3种模式大致相同。最后排序完后,会提示是否继续执行,如果不继续就返回用户功能界面,执行其他功能。
    void Good_range(sqlist Go){ Goods p; p.piece=0; p.last=0; int a,flag=0,flat=1; for(;flat!=0;){ cout<<"<按商品编号排序>请按1 <按商品单价排序>请按2 <按库存数量排序>请按3 <退出>请按0"<<endl; cin>>a; if(a!=0&&a!=1&&a!=2&&a!=3) {cout<<"选择有误,请重新输入"<<endl;continue;} switch(a){ case 1: { for(int i=0;i<Goodnum;i++){ for(int j=Goodnum;j>=i+1;j--){ if(Go.Good[j].Good_ID<Go.Good[j-1].Good_ID){//比较编号大小,小的在前面,大的在后面 flag=1; p=Go.Good[j]; Go.Good[j]=Go.Good[j-1]; Go.Good[j-1]=p; } }//jfor的 if(!flag) break; }//ifor的 cout<<"商品编号"<<setw(10)<<"商品名称"<<setw(10)<<"商品单价"<<setw(10)<<"商品库存"<<endl; for(int i=1;i<=Goodnum;i++){ cout<<Go.Good[i].Good_ID<<setw(13)<<Go.Good[i].Good_name<<setw(8)<<Go.Good[i].piece<<setw(10)<<Go.Good[i].last<<endl; } break; } case 2: 大致与编号模式相同 case 3: 大致与编号模式相同 case 0:return; }//switch的 }//最外层for的
    Good_buy函数:购买商品函数。把包含商品信息的链表Goods以及包含用户信息的链表head传入该函数,选择购买模式(输入编号或名称进行购买),如果输入的商品编号与Goods中的商品信息相同,就提示购买数量,这时如果购买数量超过库存数量会提示“库存不足”,然后重新输入;如果购买商品所支付的钱超过用户的余额,则提示“余额不足”,是否前往充值页面进行充值。充值模块下面会展示。购买完商品后,用户可以继续购买,也可以返回用户功能界面。
    void Good_buy(sqlist &Goods,clklist &head,string cID){//cID为用户帐号 string ID,name; int a,shu,sign,flat=1; cpointer p; p=custom_locate(head,cID); for(;flat!=0;){ cout<<"<输入商品编号购买>请按1 <输入商品名称购买>请按2"<<endl; cin>>a; if(a!=1&&a!=2) {cout<<"输入有误,请重新输入"<<endl;continue;} switch (a) { case 1: { cout<<"请输入商品编号:"; cin>>ID; if(Good_ender1(Goods,ID)==1) { sign=Good_locate1(Goods,ID); cout<<"请输入购买数量:"; cin>>shu; if(shu<=Goods.Good[sign].last){ if(p->money<Goods.Good[sign].piece*shu) {cout<<"余额不足,请充值!"<<endl; cout<<"是否前往充值?<充值>请按1 <否>请按0"<<endl; cin>>flat; if(flat==0) break; if(flat==1) return;} Goods.Good[sign].last=Goods.Good[sign].last-shu; cout<<"购买成功"<<endl; p->money=p->money-Goods.Good[sign].piece*shu;//账户余额减少,扣费成功 custom_save(head); cout<<"账户余额:"<<p->money<<endl; Good_save(Goods); } else cout<<"库存不足!"<<endl; } else cout<<"找不到相应商品,购买失败"<<endl; break; } case 2:与编号模式类似 }//switch的 cout<<"是否继续购物?<继续>请按1 <结束>请按0"<<endl; cin>>flat; if(flat==0) break; }//for的}
    custom_addmoney函数:余额充值函数。余额是用户帐号中的一个成员,初始为0,用户在购物的时候余额会减少,如果余额不足就无法进行购物,需要进行充值,充值是输入系统给定的充值卡卡号进行充值,这是本系统的一大亮点。把包含用户信息的链表head传入该函数,运用 p=custom_locate(head,ID);定位是哪个用户的帐号要进行充值,其中,系统设定了2种充值卡“asd500”和“asd1000”分别可以充值500和1000元,这类似与用户购买不同的充值卡给自己的账户进行充值。用户输入充值卡卡号,进行检索是否与系统设定的充值卡卡号相同,如果相同就充值相应的金额;如果不同,就提示“充值卡无效”。最后充值后,用户可以选择继续充值或者返回用户功能界面进行购物等其他操作。
    void custom_addmoney(clklist &head,string ID){ cpointer p; int sign=1; p=custom_locate(head,ID); string acard,card1="asd500",card2="asd1000"; while(sign!=0){ cout<<"请输入您获得的充值卡卡号:"; cin>>acard;//acard是用户获得的充值卡 if(acard!=card1&&acard!=card2) cout<<"充值卡无效"<<endl; if(acard==card1) { p->money+=500; cout<<"充值成功!"<<endl; cout<<"账户余额:"<<p->money<<endl; }; if(acard==card2) { p->money+=1000; cout<<"充值成功!"<<endl; cout<<"账户余额:"<<p->money<<endl; }; custom_save(head);//充值成功 cout<<"是否继续充值?<继续>请按1 <结束>请按0"<<endl; cin>>sign;//while循环判断条件,所以不需要用if }
    3 程序功能测试一开始进入系统,初次使用会提示文件打开失败,是正常的,不影响功能。由于原本的“管理员信息.txt”“普通用户信息.txt”“商品信息.txt”是空的,当录入信息就不会有提示了。进入身份选择(都要帐号和密码)

    选择1进入超级管理员,(帐号:asd,密码123),先添加管理员。

    选择1,添加管理员信息(帐号+密码+姓名),添加完后可以选择继续添加或返回

    选择2,输入要删除的管理员帐号,直接删除管理员

    添加完管理员后,可返回主菜单,进入管理员模式,输入刚刚添加的管理员帐号+密码,进入管理员模式

    功能1,进入商品录入功能,输入商品的信息(编号,名称,单价,库存),当录入成功之后,管理员可以选择继续录入或返回(该模块当录入编号和名称和已录入的信息相同会提示出错,重新录入)

    功能2,可进行商品修改,有2种模式进行选择,输入商品编号101后,显示“商品信息.txt“文件中的商品信息,而后管理员可以输入修改后的信息

    功能3,可以进行商品信息查询,可以查询到刚刚修改的联想笔记本

    功能4,可以进行删除已录入的商品信息,例如,删除刚录入的联想笔记本

    删除后,进入查询功能查看联想笔记本,不存在,表明删除成功~ ~

    功能5,进行已录入商品信息排序,可选择(编号,单价,库存)进行从小到大排序,编号也是可以的

    功能6,输出全部商品信息

    功能7,删除普通用户,如果已经有普通用户,只需要输入普通用户的帐号就可以直接删除
    进入普通用户模块,如果没有帐号是无法查看和购物的,因此消费者可以自行注册

    注册完,就可以登录进入用户模块进行购物了

    其中,商品查询,排序,库存是和管理员一样的,就不展示了
    功能4,购买商品,一开始,由于没充值时,用户余额为0,余额不足,无法购买,用户可以返回,进行充值后购买。如果充值后,余额充足就可以购买了,购买后,余额相应减少

    这里我先前往充值2000元,后购买如下,主机单价50,购买后余额为1950,扣费成功

    功能5,用户可以随时修改自己的账户密码,保护自己的财产
    功能6,就是充值了,输入系统给定的充值卡卡号,有(asd500和asd1000)

    由于刚余额1950,现在输入卡号asd1000,成功充值1000,剩余2950
    4 总结这次选择了做“超市管理系统”,在完成基本功能之后,觉得要真正实现成功的超市管理系统,必须要有个人的帐号,因此增加了普通用户注册和帐号模式,同时管理员和超级管理员也都是用帐号+密码进入。普通用户可以自行注册,拥有自己的私人帐号。同时,超市管理系统普通用户在进行购买之后,应该是要有余额的,因此添加了余额成员。拥有余额成员之后,就想到,用户需要购买东西,余额不足就需要进行充值,为了实现真实性,就选定,用户需要输入充值卡卡号进行充值。最终完美实现。
    这个系统,是使用链表实现的,由于之前的大作业是使用文件流,不是使用链表,因此这次的大作业就决定说要使用链表来实现。在编程过程中,也发现了很多的bug,最终还是一一解决。感觉自己的编程能力在不断的提高,无论是什么bug都可以自行调试解决。
    同时,在思考程序的框架功能时,也逐渐重视用户体验,人机交互。编写大作业,不再只是仅仅满足基本功能,在基本功能上,还添加其余用户体验好的功能(比如帐号登录,充值等等功能),感觉大作业让我学会了很多。
    3 评论 73 下载 2018-10-31 11:28:48 下载需要12点积分
  • 基于Nuxt2实现的个人博客系统

    摘 要当下流行的博客系统大多为开源的,用户可以根据自己的喜好配置不同风格的个人博客,但配置复杂度高,时间消耗量大。随着扁平化设计风格和前端UI框架的快速发展,打造轻量级、简约、扁平化风格同时突出核心功能的博客系统成为一大趋势。
    本文重点研究了前后端数据交互技术,用Vue框架搭建前端页面、Node.js写后端服务,Nuxt连接前端Vue和后端Node.js搭建脚手架。针对用户发表文章,不同用户用GitHub授权登录进行评论这一特定场景提出了一些具有创新性的技术方法—使用OAuth协议实现第三方授权。为程序员这类主要用户群体搭建出配置简单、内置支持Markdown语法编辑器、用户之间可进行信息交互出的扁平化个人博客系统,该系统取得了良好的运行效果。
    关键词:博客;扁平化;OAuth协议;Vue框架;Node.js
    AbstractMost popular blog systems are open source, users can configure different styles of personal blogs according to their own preferences, but the configuration complexity, time consumption is large. With the rapid development of flat design styles and front-end UI frameworks, blogging systems that create lightweight, minimalist, flat styles while highlighting core functionality have become a major trend.
    This paper focuses on the front-end data interaction technology, using Vue framework to build front-end pages, Node.js write back-end services, Nuxt connection front-end Vue and back-end Node.js to build scaffolding. In the specific scenario of posting articles for users and different users using GitHub to log in for comments, some innovative technical methods have been proposed—using the OAuth protocol to implement third-party authorization. A flat personal blog system with simple configuration, built-in support for the Markdown grammar editor, and information exchange between users has been built for a major user group such as a programmer. The system has achieved good operational results.
    Keywords: blog; flattening; OAuth protocol; Vue framework; Nod
    一、绪论1.1 课题研究背景与意义随着计算机和互联网的快速发展,传统的书信交流和现代的媒体社交方式有巨大的区别,进入信息化时代以来,涌现了很多优秀的社交网站,人们的工作、学习和交友方式发生了翻天覆地的变化。博客是一个新型的个人互联网出版工具,博客使用者可以很方便的用文字、链接、影音、图片建立起个性化的网络世界。博客秉承了个人网站的自由精神,但是综合了激发创造的新模式,使其更具开放和建设性。伴随着我国市场经济的高度发展,用户越来越依赖于大型网站提供的博客系统,如新浪博客、微博、CSDN博客等。虽然这种大型的博客系统有着设计下良好、稳定性高、知名度高等特点,但是它们不符合互联网推崇个性化发展的理念。现在越来越多的人希望有自己的站点,通过搭建真正属于自己的个人博客,提升自己的价值、更好的展现自己。
    本文的研究意义旨在如何设计出前端UI扁平化、结构清晰、核心功能齐全、技术先进的博客系统。
    1.2 国内外研究现状扁平化风格博客系统包括扁平化的设计以及博客系统的搭建,接下来将介绍扁平化设计风格和博客系统的研究现状,并介绍相应的技术进展。
    1.2.1 扁平化设计风格的研究现状在互联网快速发展和设计风格多样化的现代,扁平化设计作为一种新型的设计理念,受到越来越多人的青睐,因此扁平化设计理念越来越流行,应用范围也越来越广,从早期盛行的拟物化到现代极简主义的扁平化,这一发展历程见证了设计风格的转变以及发展趋势。扁平化设计最初是作为拟物化设计的对立面出现的。拟物化是一种对现实世界的物体或过程进行再现的风格,这种大量使用投影、真实感纹理、反射、斜面和浮雕效果。而扁平化设计不是三维的,它的名称来源于二维的特征,扁平化设计不包含任何能够带来厚度和立体感的细节特征,比如阴影、高亮或是纹理。
    当今社会信息极度爆炸,用户经常会陷入各种数据的包围中,在无限的信息中选取自己需要的内容需要消耗用户的时间和精力,扁平化的设计可以减少无用的视觉负载,优化用户对产品的使用体验感。2014年,谷歌在其I/O发布会上提出了Material Design,其设计规范覆盖了布局,控件,动画效果等多个层面,给多个平台的开发者提供了优秀的设计参考。Material Design最大的创新点是加入了卡片式设计,提升了视觉层次的清晰度,并且引入了Z轴的概念,使得各个层次的布局更加清晰。2017年微软提出了新的设计规范——Fluent Design System,改规范包含了五大核心元素,包括Light(光感)、Depth(深度)、Motion(动画)、Material(材质)和 Scale(缩放),这些元素旨在为安装Windows系统的设备提供整合交互模式、空间和全新元素的设计范式的设计语言,提升用户的使用体验。
    Material Design的加入使得安卓系统的美观度提升了一个档次,Fluent Design System的提出改善了用户对于Windows系统UI系统设计一成不变的刻板印象。
    1.2.2 国内外博客系统的发展现状随着计算机科学技术和互联网的不断发展,国内外涌现了很多优秀的博客平台,这些博客平台也多种多样,网站变得越来越简单,越来越轻量级。一般来说,用户可以借助博客平台发布博文,或者借助博客搭建工具自行搭建并发布博客。国内外比较出名的博客平台有:

    简书。简书是国内一个比较具有代表性的创作社区,任何人均可以在其上进行创作。简书提供方便易用的iOS、安卓app和网页端版本,简单优雅的设计给用户带来愉快体验感的同时还支持Markdown,使得分享交流更加方便快捷
    emlog。emlog是every memory log的简称,翻译为点滴记忆。是一款基于PHP和MySQL的功能强大的博客及CMS建站系统,致力于为用户提供快速、稳定且在使用上及其简单舒适的内容创作及站点的搭建服务
    SegmentFault。SegmentFault是中国知名地开发者社区,为为中文开发者提供一个纯粹、高质的技术交流平台。SegmentFault包括了问答平台、活动平台、个人笔记等模块,此平台基于Typecho Framework 开源框架进行开发,并使用了采用 Redis、MySQL进行数据存储,保证了该平台的稳定性。

    比较出名的博客搭建工具有:

    WordPress。WordPress开发于2003年。WordPress是一个让拥有有限技术经验的用户可以“开箱即用”使用的博客开发系统,该系统基于PHP和MySQL,核心思想是以最少的设置工作让可以专注于自由分享故事,产品或服务。WordPress能够构建任何类型的网站,包括博客,如果使用自托管的WordPress,则可以接触到数以千计的插件和主题资源,精通技术的用户可以用非凡的方式对其进行定制。WordPress方便地为用户提供了创建和分享的机会,从手工制作的个人轶事到改变世界的动作
    Hexo。Hexo是基于node.js开发的静态博客开发框架,拥有依赖少,易于安装使用等特点。Hexo可以从markdown文件中方便的生成静态网页托管在GitHub和Coding上,直接在GitHub平台托管博客,用户可以安心的去写作而不需要定期维护。使用hexo搭建博客,用户还可通过hexo添加各种功能,包括搜索的SEO,阅读量统计、访问量统计和评论系统等,极大地提升了用户的博客使用体验

    1.3 本论文主要工作与章节安排本文共分为八章,各章节内容安排如下:

    第一章主要叙述了基于 Vue 和 express 的扁平化个人博客系统的选题背景、扁平化设计风格的研究现状以及博客系统的国内外发展现状、相关的工作安排
    第二章主要叙述了本系统使用到的主要技术介绍
    第三章主要叙述了本系统设计思路以及需求分析
    第四章主要叙述了系统的结构设计,包括前端UI、API接口、服务器的配置
    第五章主要叙述了系统如何使用mongodb数据库进行数据存储以及表结构的设计
    第六章主要叙述了系统各个模块的实现以及主要功能
    第七章主要叙述了如何将系统从本地部署到服务器上
    第八章是本文的总结与展望,是对本文内容的整体性总结以及对未来工作的展望

    二、技术介绍为了更好的了解本系统,以下将对本文用到的技术及框架一一进行简介:
    2.1 Vue框架简介Vue是一套用于构建用户界面的前端渐进式框架。Vue使用声明式渲染技术,能够将数据绑定到DOM文本或者特性。在绑定到DOM之后,Vue提供了一个强大的过渡效果系统,可以在元素发生位置变换的时候,自动地应用过渡效果。并且,Vue以组件化的思想去构建前端页面,一个组件可以是一个页面,也可以是一个通用的元素,不同的组件之间可以相互传参(如子组件间传参、父组件间传参,子组件向父组件传参,父组件向子组件传参),以完成必要的数据传输工作。在本设计中,定义了多个Vue的组件用于博客系统的搭建,组件的应用使得系统的实现变得高效而具有模块复用性。
    2.2 NuxtNuxt.js是一个基于Vue.js的通用应用框架。通过对客户端/服务端基础架构的抽象组织,Nuxt.js主要关注的是应用的UI渲染。Nuxt.js 集成了Vue2、Vue-Router、Vuex、Vue-Meta,用于开发完整而强大的 Web 应用。Nuxt.js是一个基于Vue.js的通用应用框架。通过对客户端/服务端基础架构的抽象组织,Nuxt.js主要关注的是应用的UI渲染。Nuxt.js 集成了Vue2、Vue-Router、Vuex、Vue-Meta,用于开发完整而强大的 Web 应用。Nuxt.js使用Webpack和vue-loader、babel-loader来处理代码的自动化构建工作(如打包、代码分层、压缩等等)。
    2.3 Node.jsnodejs是基于Chrome的V8内核引擎开发的一个JavaScript解释器,目的是提供一个可以让JavaScript高性能运行的环境。 Nodejs底层使用C++进行编写,拥有强大的I/O性能,并且Nodejs拥有完善的时间处理机制,天然可处理DOM,使得JavaScript书写的程序拥有较高的性能。拥有最早Node.js主要是安装在服务器上,后来Node.js在前端也大放异彩,带来了Web前端开发的革命。
    2.4 Mongoose数据库MongoDB是一个非关系型数据库。由于关系型数据库需要满足三范式,不存储冗余数据,数据量较大需要做关联查询时效率较低,而非关系型数据库不需要严格满足三范式,可以将相关的数据存在同一个文件中,提高查询效率。和传统的关系型数据库不一样,MongoDB不需要显式地在系统中某一处创建数据库,而是在用户第一次向数据库写入数据时会,将会在指定的目录下自动创建数据库,并在磁盘上分配一系列数据库文件集合,包括所有的集合、索引以及其他元数据。
    Mongodb使用BSON进行数据的存储,BSON与JSON格式有相似的结构,都是以object、key-value格式进行数据的存储。由于现在互联网中大多数项目的前后端都是通过API请求来进行数据交互,而JSON是当前主流的交互数据格式, 使用Mongodb作为数据的存储平台,使得在使用API进行数据交互时不需要额外的数据格式转换,大大方便了开发人员的开发体验
    2.5 OAuth协议OAuth(开放授权)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源,如照片、视频、联系人列表,而无需将用户名和密码提供给第三方应用。OAuth授权流程如图2-1所示:

    流程图说明:

    用户点击客户端提供的授权请求
    客户端请求服务的授权页面呈现给用户,用户点击确认授权后服务端返回授权许可凭证给客户端
    客户端通过步骤二接收到的授权许可凭证及在服务端注册的应用信息请求服务端
    如果步骤三验证通过服务端则返回 access token 给客户端
    客户端通过第四步获取的 access token 请求服务端获取资源
    如果服务端校验 access token 成功,则返回指定资源给客户端

    三、系统需求分析3.1 设计目标本文旨在设计出一款前端UI页面扁平化、架构扁平化(组件之间相互独立,可复用)、支持markdown语法、核心功能突出的个人博客系统,一键注册拥有自己的个人博客系统,无需繁琐的搭建、配置过程,降低操作的复杂度。本系统的目标用户程序员,支持Markdown语法这一特性使得程序员写技术博客时代码区域有高亮效果,代码和其他的文本区分开来,在视觉上阅读更加的方便。
    3.2 系统的用户角色及权限本系统的用户角色分为三类,分别是管理员、注册用户以及游客,以下是阐述不同的用户角色拥有的权限:

    管理员

    可删除任何用户发布的文章可删除任何用户的评论
    注册用户

    可新建文章可删除或编辑自己发布的文章可查看、编辑或删除自己的草稿可设置自己的个人信息及修改密码
    游客

    可查看所有已发布的文章可通过GitHub登录对文章进行评论

    3.3 模块需求分析本系统一共分为八大模块,分别是登录、注册、文章发布、文章管理、草稿、评论、标签、设置模块,以下是对各个模块需求的详细分析:
    3.3.1 登录模块登录模块的功能分为前端界面的设计和后端账户数据的存储及校验。前端UI界面要求界面简洁、颜色以清新风格为主,可采用大片纯色填充的方式。后端数据存储,要求将用户名和密码存在user表中,且密码采用MD5加密算法加密成等长的字符串后存储在user表中,从数据安全性角度来看,不允许密码采用明文的方式直接存储,加密后的等长字符串能提高用户数据的安全性。
    3.3.2 注册模块注册模块的功能分为前端页面的设计和后端新账户数据的存储,以及注册新账户遵守以下校验点:

    用户名和数据库中已经存在账户进行比对,若已存在此用户则不能创建成功
    若用户名和密码为空,则不能创建成功
    新账户的密码长度不能低于3位

    3.3.3 新建文章模块用户通过新建文章模块进行文章的编辑和发布,文章编辑区的编辑器支在线编辑和Markdown语法,这也是本博客系统和传统的博客系统不一样的地方,现在比较受程序员欢迎的博客系统有Hexo等,但不支持在线编辑,只能在本地编辑好文章再通过git命令发布到博客上。
    3.3.4 文章管理模块文章管理是本系统的核心功能之一,管理好文章的渲染以及分类,提高用户的体验感,文章管理遵循以下要求:

    用户发布文章有种方式可选择:置顶、首页显示、标签页显示、草稿
    选择置顶的文章显示在首页列表的最顶部
    首页显示的文章渲染在首页列表中,未登录的游客浏览本博客系统时也可以看得到所有用户发的文章
    标签页显示的文章在标签页根据标签分类显示
    草稿类的文章渲染在草稿页面,这部分的功能是暂时存储未完成编辑的文章,方便用户下次继续编辑

    3.3.5 草稿模块在草稿模块,将所有用户编辑文章时选择为草稿存储的文章归类整理显示在草稿页面,用户可以继续编辑或删除,编辑完成后再发布显示在首页文章列表。
    3.3.6 评论模块评论模块记录所有用户或者游客通过GitHub授权登录后对文章进行的评论,这一部分的数据是不同用户交互最直接的体现。与其他博客系统不一样的地方是,游客对本系统的文章进行评论后,系统会给文章的作者发邮件通知,邮件格式为“XXX对你的文章进行了评论”“XXX回复了你的评论”。
    3.3.7 标签模块用户在新建文章页的底部通过回车创建标签,这些标签除了在文章编辑器底部显示之外,还要在标签页将标签的内容以及创建时间显示出来,点击每一个标签,能将该标签下的所有文章显示出来。
    3.3.8 设置模块设置模块分为两部分,第一部分是用户的基本信息设置,其中一个输入框,在代码里的变量名为Nickname的字段,也译为这个博客头部的别名显示,用户可根据自己的喜好给自己的博客系统命名;第二部分是修改密码,用户通过此部分修改密码功能改自己账户的密码。
    四、系统结构设计4.1 系统页面架构设计本系统的前端页面设计分为三级,第一级为登录注册页面,第二级为首页和标签页,第三级为首页中的四个子页面,分别为新建文章页、高草页面、评论页面和设置页面;标签页面包含一个子页面标签文章页。本系统总体架构图如图4-1所示:

    4.2 系统技术架构设计本系统用了Nuxt.js框架,Nuxt.js分为客户端(Client)和服务器端(Server)如图4-2所示。以下将从客户端和服务端进行阐述:
    客户端:使用Vue框架,以组件的方式设计页面,Vuex记录参数状态,方便组件间的传参,通过API接口在服务器端去调用数据库里的数据,最后将数据渲染在UI页面上。
    服务器端:采用Node.js来写后端的API接口以及构建服务器,API接口去调用Mongodb的数据,最后传给对应的前端页面。

    4.3 系统数据流设计本系统的数据流分为用户名和密码、文章在不同的页面上的渲染、标签的渲染以及评论数据的渲染,数据流如图4-3所示:

    五、数据库设计5.1 表设计本系统主要的用到的表有Article、User、tags、Comment。以首页看到的一篇文章为例,url=http://8.6.8.244:3000/detail/5c98f79cad892945422d15df 。其中“5c98f79cad892945422d15df”是数据写入时数据库自动生成的_id,用来标识这篇文章。当我们请求这篇读取某篇文章时,会先拿到以上所述的_id,然后在Article表中进行比对,找到对应的_id,并且在Article里根据author这个字段去拿到User表中的用户信息,根据comment这个字段拿到Comment表中的信息,根据tags这个字段去拿到tags表中相关的信息。如图5-1所示:

    5.2 表实现本系统用Mongodb数据库进行数据存储,Mongodb的存储格式是BSON形式,以配置文件的形式而非表格的形式存储。每个表都有对应的Schema,一个Schema对应一个表,并且有不同的字段进行信息分类存储。图5-2为Mongodb的管理界面:

    本系统一共设计了三个表,分别为文章表、标签表、用户表。
    5.2.1 文章表文章表以BSON格式存储着所有发布的文章,一共有13个字段:_id、views、flag、like、comments、tags、previous article、next article、title、content、created、created at、update at、_v.

    5.2.2 标签表标签表一共有5个字段:_id、name、created at、updated at、_v

    5.2.3 用户表用户表一共有14个字段:_id、role、email、nickname、motto、avatar、following、followers、like、username、password、created_at、update_at、_v。


    六、系统的实现6.1 登录模块登录模块UI页面如图6-1所示,由<h2>标题LOGIN和两个\<input\>标签以及带高亮的登录按钮和白色透明的注册按钮组成,登录按钮进行高亮的设计,体现在登录页面登录优先级更高于注册,背景为大片的白色填充,简单的视觉设计给用户减少不必要的视觉负担。

    用户进行登录时,输入已经在数据库存在的用户名和密码,系统首先对用户输入的用户名进行查找,若数据库中存在次用户名,则进一步的对用户输入的密码进行校验。本系统的密码采用MD5加密,生成一串等长的加密后的字符串存在数据库中,用户在前端输入密码后,后台将密码用MD5算法加密后,用加密后的字符串在数据库中进行查询比对,若存在一样的加密后的字符串,则校验成功,用户可以登录成功。由图6-2、6-3可以看到,不同的密码加密后的字符串是不一样且唯一的。


    6.2 注册模块注册模块UI页面如图6-4所示,由<h2>标题REGISTER和两个<input>标签以及带高亮的注册按钮和白色透明的登录按钮组成,注册按钮进行高亮的设计,体现在注册页面注册优先级更高于登录,背景为大片的白色填充,简单的视觉设计给用户减少不必要的视觉负担。
    遵循本系统“一键注册拥有个人博客系统”的核心思想,用户在注册模块中的操作很简单,只需输入用户名和密码后点击注册就能拥有属于自己的博客系统。

    用户输入用户名和密码进行注册时,系统有以下提示点:用户名和密码不能为空,如图6-5所示:

    6.3 文章管理模块首页是用户发布的所有文章列表,如图6-8所示。置顶的设置可以提醒浏览博客的用户重点的去查看此篇文章。

    首页的顶部有搜索功能,支持关键字模糊查询,用户可以直接输入关键字去搜索到自己想看的文章,而不需要一页一页的翻,如图6-9所示:

    6.4 支持Markdown语法的编辑器新建文章模块内置支持Markdown语法的编辑器,有置顶、首页显示、标签页显示、草稿四种选项,可上传图片、进入预览模式、以及一键保存按钮。在编辑器的底部有“回车创建标签”的输入框,完成文章的编辑后可给此文章添加一个标签,有回车新建标签和选择现有的标签两种方式,添加标签后,该文章在标签页对应的标签分类下显示,归类管理文章能让用户快速的找到自己想看的内容,也使得整个系统的文章管理井然有序

    编辑器支持Markdown语法,左边是编辑区,右边是渲染后的结果,如图6-11所示。以下介绍几种基本的markdown语法:

    分点显示 输入*后空格
    代码高亮 ``` 代码区域 ```
    一级标题 #
    二级标题 ##
    三级标题 ###
    引用 > XXX


    6.5 草稿模块草稿页面显示所有未完成编辑的文章,用户可对草稿类的文章进行编辑或删除操作,编辑完成之后点击发布即可在首页文章列表显示。

    6.6 评论模块用户对文章进行评论,首先,先进行GitHub授权登录,如图6-13、6-14所示:

    GitHub授权登录成功后,可对文章进行评论。如图6-15所示:
    所有用户的评论都被渲染在前端的评论页面,如下图所示,评论的内容及评论的时间,点击评论可跳转到相应的文章,不同的用户通过GitHub授权登录后在评论区进行学术的交流以及信息的互动。

    6.7 OAuth授权协议过程用户通过GitHub账号登录进行评论的核心技术是OAuth授权,详细过程如图6-17所示。以下是对于本系统使用的OAuth授权过程做详细的介绍:

    用户点击页面上的“GitHub授权”按钮后,后台调用API接口login/Oauth/authorise
    拿到Client id和callback URL和生成的Code,根据拿到的callback URL跳转到GitHub登录页面,用户数据GitHub用户名和密码点击登录时,Code导出并调用API接口OAuth/access token,拿到生成的token去访问GitHub的用户信息,授权成功

    以上的授权过程主要分为网站和GitHub的协商以及用户和GitHub的协商
    网站和GitHub的协商:

    GitHub会对用户的权限进行分类,如读写仓库信息的权限、读取用户信息的权限、修改用户信息的权限。如果某网站需要获取GitHub用户的信息,那么GitHub先要求本系统在GitHub平台上注册一个应用,并且填写系统的域名和标明期望获取的用户信息,此时GitHub只允许该域名能获取到用户信息,系统和GitHub达成共识,拿到Client id和Client Secret
    用户和GitHub的协商:

    用户点击GitHub登录按钮时,本系统把拿到的Client id交给用户,进入GitHub授权页面时通过Client id的认证,告知用户本系统想读取用户信息,若用户点击授权登录,则GitHub把用户信息发送给系统,认证成功,否则认证失败
    6.8 标签模块标签模块页显示所有被创建的标签,标签的名字及创建的时间,管理员可对此标签进行编辑或删除。点击某一个标签可以显示该标签下所有的文章。

    6.9 设置模块设置模块分为两个部分,第一部分是修改个人信息,第二部分是修改密码,用户可以在设置页面进行密码的修改,数据库会随之更新用户的密码,用MD5加密算法加密后存储在数据库中。
    6.10 侧边栏模块侧边栏有四个按钮,分别是返回登录页面、查看xml文件配置、跳转到github首页、返回顶部按钮。如图6-20所示:

    七、系统部署到服务器上7.1 硬件配置vultr是以服务器的使用时长计费的服务器提供商,其拥有较好的网络带宽和能提供稳定的服务器。考虑到本系统需要使用较新版本的nodejs及npm包,并且需要使用比较现代的前端框架,故在vultr选配了64位的Ubuntu系统虚拟服务器。其拥有1GB的运行内存和25GB的高速硬盘,足以应付一个博客系统。
    7.2 登录服务器我们使用 SSH 的方法访问远程服务器,接着进行相关的环境配置。现有一款全平台通用的终端工具Termius,其支持通过SSH的方式访问vultr服务器,添加一个服务器步骤如下:
    点击添加一个host,如图7-2所示:

    填写ip地址、用户名和密码等, 如图7-3所示:

    打开“SSH”,填写完毕后按”SAVE”按钮进行保存, 如图7-4所示:

    尝试登陆到远程服务器, 如图7-5所示:

    7.3 部署Node.js环境打开ssh终端后,在终端下依次输入:
    更新远程服务器安装包索引缓存
    sudo apt update从安装包索引库中安装 Nodejs
    sudo apt install nodejs安装npm命令行工具
    sudo apt install npm可输入打印版本号验证安装成功性
    ➜ ~ node -vv8.10.0➜ ~ npm -v6.9.07.4 部署Mongodb数据库打开ssh终端后,在终端下输入:sudo apt install mongodb,此时,mongodb的核心服务、命令行工具都会被成功安装,使用打印版本号命令验证安装成功性:
    ➜ ~ mongo -versionMongoDB shell version v3.6.3git version: 9586e557d54ef70f9ca4b43c26892cd55257e1a5OpenSSL version: OpenSSL 1.1.0g 2 Nov 2017allocator: tcmallocmodules: nonebuild environment: distarch: x86_64 target_arch: x86_64安装完毕后,在/下创建数据库存储文件夹/data/db
    7.5 文件上传到服务器使用 filezllia工具将文件上传到服务器,按照以下步骤添加服务器:

    点击”连接”,filezllia将会连接站点
    连接到远程站点后,把需要上传的文件拖拽到需要上传到的文件夹内即可。

    7.6 数据库迁移使用mongodump和mongorestore分别完成本地数据库的压缩备份和还原。
    从本地mongo数据库中执行压缩备份,如对于”essay”数据库
    mongodump --db essay接着将会得到 dump.zip,内部结构为
    dump/ └── essay ├── articles.bson ├── articles.metadata.json ├── tags.bson ├── tags.metadata.json ├── users.bson └── users.metadata.json将导出文件通过filezilla上传到远程服务器,并解压为essay 文件夹
    从解压文件夹中还原数据到远程服务器mongo数据库中,如恢复essay数据库
    mongorestore -d essay /dump/essay进入mongo命令行,查看是否成功导入
    ➜ ~ mongo...> show dbsessay 0.000GB八、总结和展望8.1 本文总结本文针对当下流行的扁平化设计风格搭建个人博客系统进行了一系列研究,重点对如何构建一个前端UI设计扁平化、核心功能突出、第三方授权以及不同用户能够进行信息交互等关键问题进行了深入的研究。本文的主要工作和创新点如下:

    针对已存在的博客平台配置复杂度高的不足之处,开发出了一键注册拥有个人博客的系统
    针对个人博客使用者大多数为学习技术的程序员,且当前的博客系统没有支持markdown语法的在线编辑器的这一现象,开发出支持在线编辑支持Markdown语法的博客系统
    针对博客文章传播途径较窄这一痛点问题提出了支持github第三方授权登录进行信息交互,提高文章的传播率的解决方案

    8.2 不足之处和未来的工作计划现今计算机科学技术和互联网发展越来越快,前端领域的框架和技术层数不穷,许多优秀的框架和技术能开发出更优秀完美的博客系统。轻量级的产品现在越来越流行,但要做到轻量级而又功能齐全,体验极佳还有待提高技术水平以及优化设计。以下是本文提出的搭建博客系统中的不足之处以及下一步工作计划:

    本文提出的扁平化设计虽然比其他博客平台都要更简洁,但是再UI的设计上还未达到使用户除了使用舒适的同时有很棒的视觉冲击。下一步,我们将进一步探索更优秀的扁平化UI设计,在各个页面和整体设计上进行优化,进一步提高本系统的视觉冲击力
    本文提出的不同用户信息交互只能通过GitHub账户登录授权才能进行,这使得不是github用户的人在本系统上无法和其他用户进行信息的交互。下一步,我们将尝试添加更多的授权登录方式,如支持社交账号登录授权等
    本文的博客系统首页显示的文章是用户发布的所有文章的列表,置顶状态的文章也是发布者手动设置的,在当今信息爆炸的时代,提高用户的使用体验感的途径之一是根据用户的浏览喜好推荐与当前用户最匹配的数据,并渲染在首页顶端,本文的系统目前还未做到引入推荐算法,没有智能推荐文章功能。下一步,我们将对推荐算法进行深入的学习并把它引入本系统中来

    参考文献[1]微博客传播特性及盈利模式分析[J].卢金珠.现代传播(中国传媒大学学报).2010
    [2]陈沛捷.个人博客系统设计与实现[D].硕士学位论文天津大学,2017.
    [3]殷红梅.基于扁平化艺术设计风格的UI设计探究[J].电脑编程技巧与维护,2018. [4]任葆轩.试论拟物化与扁平化在UI设计中的比较[J].艺术科技.2017(11)
    [5]钱瑜.浅析UI设计中拟物化和扁平化的发展[J].新丝路(下旬).2016(05)
    [6]张思南.极简主义在扁平化风格UI设计中的应用与研究[J].美术教育研究.2018(02)
    [7]陈琳.如何购买WordPress网站域名及绑定域名[J].计算机与网络,2018,44(10):35.
    [8]殷丽萍.Tumblr:互联网上的“极简”奇迹[J].中外管理,2015(03):36-37.
    [9]王丹阳.浅析简书的商业模式[J].新闻研究导刊,2019,10(01):212-213.
    [10]梁额.《Vue.js实战》,清华大学出版社,2017.
    [11]梁睿坤.《Vue2实践揭秘》,电子工业出版社,2017.4ISBN978-7-121-31068-3
    [12]《Vue.js2Web Development Projects》,Copyright © 2017.
    [13]《Full-Stack Web Development with Vue.js and Node》,Copyright © 2018.
    [14]亚历克斯 • 杨 布拉德利 • 马克 麦克 • 坎特伦蒂姆 • 奥克斯利 马克 • 哈特 T.J. 霍洛瓦丘 内森 • 拉伊利赫,《Node.js实战》,—北京:人民邮电出版社,2018.08
    [15]邹竞莹. Node.JS博客系统的设计与实现[D].硕士学位论文黑龙江大学,2016.
    [16]吴德,应毅,毛道鹤.基于OAuth2.0的认证授权方案设计与优化[J].软件,2018
    [17]沈桐,王勇,刘俊艳.基于OAuth2.0,OpenID Connect和UMA的用户认证授权系统架构[J].软件,2017,38(11):160-167.
    [18]李纪伟,段中帅,王顺晔.非结构化数据库MongoDB的数据存储[J].电脑知识与技术,2018,14(27):7-9.
    [19]杨怀,宋俊芳,王聪华.浅谈MD5加密算法在网络安全中的应用[J].网络安全技术与应用,2018(09):40.
    [20]徐跃,吴晓刚.一种改进的MD5加密算法及应用[J].现代计算机(专业版),2018.
    [21]任杰麟.MD5加密算法的安全性分析与改进[J].农业图书情报学刊,2017.
    0 评论 1 下载 2019-07-01 23:13:02 下载需要15点积分
  • 基于C#和SQL SERVER的汽车配件仓储管理系统

    摘 要汽车配件信息管理网站建设是汽车配件信息进行有效管理的重要工具。本次设计以建设汽车配件信息管理网站为目标,同时结合现今对信息管理相关网站建设方面的需求,建设相关后台数据库及相应的交互性界面。
    为了实现有效率的对汽车配件信息进行管理,本论文以网站建设为主体架构,详细的介绍了汽车配件信息管理网站的需求,具体描述了整个网站的开发过程,分析了汽车配件信息管理网站的功能。并以业务流程图的形式详细的介绍了系统的各个功能的模块及各个模块的数据库表,并详细的记录了各个功能测试的情况信息。对于汽车配件管理系统迅速发展的信息化时代,通过规范、统一的网站式管理对相关信息进行整合,不仅方便了管理,而且提高了工作效率。
    本毕业设计的内容是设计并且实现一个汽车配件信息管理网站设计,用Microsoft Visual Studio作为开发工具,以SQLServer作为数据库,使用C#语言开发。该系统界面友好、操作简单,容易维护,适合公司管理者以及员工使用。
    关键词:汽车配件信息;网站式管理;MicrosoftVisual Studio;SQL Server
    AbstractThe construction site is automobile fittings information management of autoparts information to efficient management. The design of the construction ofauto parts information management website as the goal, combined with thecurrent information management related to website construction needs, theconstruction of relevant background database and the corresponding interactiveinterface.
    In order to realize the efficient management of auto parts information, thewebsite construction as the main structure, detailed introduces the auto partsinformation management needs of the site, the specific description of thedevelopment process of the entire site, analysis of the auto parts information managementfunctions of the website. And the business process diagram in the form ofdetailed description of the module and each module of the system each functionof the database table, and detailed records of each function test information.In the information age, the rapid development of auto parts management systemthrough the website management standard, unified integration of relevantinformation, not only convenient management, but also improve the workEfficiency at
    The content of this graduation design is to design and implement an autoparts information management website design, using Microsoft Visual Studio as adevelopment tool, using SQL as the database Server, using C# languagedevelopment. The system has a friendly interface, simple operation, easymaintenance, suitable for company managers and employees.
    Key words: auto partsinformation; Web site management; Microsoft Visual Studio; SQL Server
    引言随着计算机技术的快速发展,许多企业事业单位的管理都实现了办公自动化,这种自动化管理方式不仅管理简单,而且效率非常高。为了能够高效而且有效地管理汽车配件的管理信息,汽车配件经营者提出使用计算机进行汽车配件信息的管理,使汽车配件管理科学化,最大限度地减少信息损失,提高汽车配件的利益。
    使用计算机管理汽车配件相对人工记录,有很多的有点。首先,用计算机进行金额计算时速度快,可信度高。而且查询时不必要逐个查找,只需要输入相关信息就可快速得到结果。然后,汽车配件信息存储在计算机,可以作到数据的永久保存,安全可靠。最重要的是,汽车配件数据存储在计算机中,由于计算机存储容量非常大,所以清单的内容在输入电脑后,对数据的操作是非常方便的,而且避免了频繁的使用清单。
    汽车部件仓储管理已经渐渐的走向稳定发展的趋势,更加具有企业化的概念,在体制上,汽车部件仓储管理已经开始慢慢的健全它的体制,对公司人员进行培训,将业务进行熟练化,这样大大的提高了汽车部件管理在世界中的发展,并且使它的地位明显的上升,也进一步的满足了大家的需求。为了更好的发展,企业渐渐的从整体中分离开来建立自己的发展模块,不断的寻求发展模式,扩大自己的经营模式。
    本网站使用.net+Microsoft Visual Studio+SQL Server的组合,使用计算机浏览器实现了网站的基本功能,网站对汽车配件的信息进行管理,不但可以使用工程的、规范的管理过程,而且可以有效的提高了工作人员的工作效率,直观的、科学的管理汽车配件信息,进而完成公司的业务,这对汽车配件信息的管理的发展及信息化的管理具有极其重要的意义。
    第1章 绪 论1.1 论文背景1.基于汽车配件管理的相关背景
    汽车配件管理系统的目的是为企业提供一个计算机化的管理平台,实践企业内部科学有效的管理,促进企业管理信息化,规范化,将能使管理人员从繁琐的杂务工作中解脱出来,真正从事管理工作。
    目前汽车配件销售企业大多数在其连锁店的管理还是手工进行,随着汽车配件行业的迅速发展,手工管理的种种弊端暴露无疑,给销售企业的发展带来了不必要的麻烦。为了规范企业内部管理,提高企业业务管理水平,更好的为客户服务,应采用计算机来管理汽车配件的进销存业务。
    汽车部件仓储管理已经渐渐的走向稳定发展的趋势,更加具有企业化的概念,在体制上,汽车部件仓储管理已经开始慢慢的健全它的体制,对公司人员进行培训,将业务进行熟练化,这样大大的提高了汽车部件管理在世界中的发展,并且使它的地位明显的上升,也进一步的满足了大家的需求。为了更好的发展,企业渐渐的从整体中分离开来建立自己的发展模块,不断的寻求发展模式,扩大自己的经营模式。
    2.数据管理技术
    随着互联网发展进程的加快, 信息资源网络化成为一大潮流。与传统信息资源相比, 网络信息资源在 数量、结构、内涵、类型、载体形态、分布和传播范围、控制机制、传递手段等方面都与传统信息资源有显著的差异, 呈现出许多新的特点。这些新的特点赋予网 络环境下信息资源管理许多新的内涵。网络信息资 源管理建立在新的社会基础结构即信息网络的基础之上, 适应了信息化社会信息组织和管理的需要是一个新的生长点。
    数据管理技术就是指人们对数据进行收集、组织、存储、加工、传播和利用的一系列活动的总和,经历了人工管理、文件管理、数据库管理三个阶段[4]。每一阶段的发展以数据存储冗余不断减小、数据独立性不断增强、数据操作更加方便和简单为标志,各有各的特点。其中现今就处于数据库管理为主流的阶段,节省的大量的人力资源,对于信息化网络化的现在数据库管理技术的共享性、大量数据存储显示、数据处理快速而且具有很高的安全性和完整性,其并发控制和恢复性都不会让数据轻易丢失
    3..NET技术
    ASP.NET是由微软在.NET Framework框架中所提供,开发Web应用程序的类库,封装在System.Web.dll文件中,显露出System.Web名字空间,并提供ASP.NET网页处理、扩充以及HTTP通道的应用程序与通信处理等工作,以及Web Service的基础架构。ASP.NET是ASP技术的后继者,但它的发展性要比ASP技术要强大许多。
    ASP.NET可以运行在安装了.NET Framework的IIS服务器上,若要在非微软的平台上运行,则需要使用Mono平台[2],ASP.NET在2.0版本已经定型,在.NET Framework 3.5上则加上了许多功能,像是ASP.NET AJAX、ASP.NET MVC Framework、ASP.NET Dynamic Data与Microsoft Silverlight的服务器控件等。
    很多人都把 ASP.NET 当做是一种编程语言,但它实际上只是一个由 .NET Framework提供的一种开发平台 (development platform),并非编程语言。也可认为ASP.NET是.NET组件,任何.NET语言,例如C#,可以引用该组件,创建网页或Web服务。
    为了因应云化所诱发的多作业平台集成与开发能力,微软特别开发一个新一代的 ASP.NET,称为 ASP.NET vNext,并于 2014 年命名为 ASP.NET 5,但随后于 2016 年将它更名为 ASP.NET Core,由于架构上的差异颇大,因此未来 ASP.NET 与 ASP.NET Core 将是分别发展与维护,Windows 平台的 ASP.NET 4.6 以上版本仍维持 Windows Only,但 ASP.NET Core 则是具有跨平台 (Windows, Mac OSX 与 Linux) 的能力。
    1.2 系统开发的意义随着计算机技术的快速发展,许多企业事业单位的管理都实现了办公自动化,这种自动化管理方式不仅管理简单,而且效率非常高。为了能够高效而且有效地管理汽车配件的管理信息,汽车配件经营者提出使用计算机进行汽车配件信息的管理,使汽车配件管理科学化,最大限度地减少信息损失,提高汽车配件的利益。
    使用计算机管理汽车配件相对人工记录,有很多的优点。首先,用计算机进行金额计算时速度快,可信度高。而且查询时不必要逐个查找,只需要输入相关信息就可快速得到结果。然后,汽车配件信息存储在计算机,可以作到数据的永久保存,安全可靠。最重要的是,汽车配件数据存储在计算机中,由于计算机存储容量非常大,所以清单的内容在输入电脑后,对数据的操作是非常方便的,而且避免了频繁的使用清单。
    1.3 研究现状和发展趋势1.研究现状
    现在我国的企业特别是汽车配件企业的管理水平还停留在纸介质或半自动(由电脑处理一部分数据,由人工处理一部分数据)的基础上,这样的机制已经不能适应时代的发展,因为它浪费了许多人力和物力,在信息时代这种传统的管理方式必然会被以计算机为基础的信息管理所取代。软件作为一项有力的工具,只能当此种工具,与我们的实践相结合起来的时候,才具有重大的社会价值及使用价值。因此根据企业目前实际的汽车配件管理系统情况开发一套汽车配件管理系统是十分有必要的。
    2.发展趋势
    汽车部件仓储管理已经渐渐的走向稳定发展的趋势,更加具有企业化的概念,在体制上,汽车部件仓储管理已经开始慢慢的健全它的体制,对公司人员进行培训,将业务进行熟练化,这样大大的提高了汽车部件管理在世界中的发展,并且使它的地位明显的上升,也进一步的满足了大家的需求。为了更好的发展,企业渐渐的从整体中分离开来建立自己的发展模块,不断的寻求发展模式,扩大自己的经营模式。
    通过计算机进行货物的进入和销售数量的统计,使管理者不必再为统计数量而感到烦恼,可以利用节省出来的时间全身心的投入到其他事情当中,也对管理制度进行了优化和改良,集中对零部件进行统计和分配,这样不仅减少我们使用的资源,也大大的降低了我们的劳动成本,节省了财力,使管理人员更加专心的从事管理工作,使管理制度更加合理化和规范化。
    1.4 论文的基本结构此次论文详细的介绍了汽车配件信息管理网站设计及实现的过程以及相关功能解说、研究思路、思想总结等部分。着重介绍了这个网站的设计思想、技术路线、开发平台的选择、总体框架、程序流程、本网站应实现的功能以及具体的实现方法和步骤,并且讨论类似网站存在的局限性和解决的思路。其主要基本结构如下:

    绪论,用来详细解说该系统的背景、研究意义及研究前景。
    系统开发平台的介绍,用来详细介绍该系统用到的开发工具,如Microsoft VisualStudio,SQLServer。
    第1章是需求分析,用来分析经济、技术、操作上的可行性,重点介绍系统的功能分析、业务流程图以及ER图等。
    第2章是系统设计,用来详细介绍各个模块的功能,并设计数据库,展示系统界面。
    第3章是实现部分功能的主要代码及系统的整体及相关功能测试,用来展示系统的核心代码以及展示系统测试的效果。

    第2章 系统开发工具及相关技术本章将对本次汽车配件信息管理网站的开发平台进行简要的介绍,同时介绍在开发过程中采用的一些Ajax技术。
    2.1 开发工具简介本次网站设计主要采用的是学校中通用的软件,操作系统是Windows,主要Web端和后端的开发都是在Microsoft Visual Studio中操作实现,数据库采用的是Sql Server数据库。下面将对两个软件简要介绍:
    1.Microsoft Visual Studio
    Microsoft Visual Studio(简称VS)是微软公司的开发工具包系列产品。VS是一个基本完整的开发工具集,它包括了整个软件生命周期中所需要的大部分工具,如UML工具、代码管控工具、集成开发环境(IDE)等等。所写的目标代码适用于微软支持的所有平台,包括MicrosoftWindows、Windows Phone、WindowsCE、.NET Framework、.NET CompactFramework和Microsoft Silverlight。
    而Visual Studio .NET是用于快速生成企业级ASP.NET Web应用程序和高性能桌面应用程序的工具。Visual Studio包含基于组件的开发工具(如Visual C#、Visual J#、VisualBasic和Visual C++),以及许多用于简化基于小组的解决方案的设计、开发和部署的其他技术。
    2.Sql Server
    SQL Server一开始并不是微软自己研发的产品,而是当时为了要和IBM竞争时,与Sybase合作所产生的,其最早的发展者是Sybase[1],同时微软也和Sybase合作过SQL Server 4.2版本的研发,微软亦将SQL Server 4.2移植到Windows NT(当时为3.1版),在与Sybase终止合作关系后,自力开发出SQL Server 6.0版,往后的SQL Server即均由微软自行研发。
    Sql Server是一个关系数据库管理系统。具有易用性、适合分布式组织的可伸缩性、用于决策支持的数据仓库功能、与许多其他服务器软件紧密关联的集成性、良好的性价比等。为数据管理与分析带来了灵活性,允许单位在快速变化的环境中从容响应,从而获得竞争优势。
    维基百科给的解释是这样的,Microsoft SQL Server是由美国微软公司所推出关系数据库解决方案,最新的版本是SQL Server 2016,已经在2016年6月1日发布。 数据库的内置语言原本是采用美国标准局(ANSI)和国际标准组织(ISO)所定义的SQL语言,但是微软公司对它进行了部分扩充而成为作业用SQL(Transact-SQL)。几个初始版本适用于中小企业数据库管理,但是近年来它的应用范围有所扩展,已经触及到大型、跨国企业的数据库管理。
    2.2 .net 平台介绍.NET就是微软用来实现XML,Web Services,SOA(面向服务的体系结构service-oriented architecture)和敏捷性的技术。对技术人员,想真正了解什么是.NET,必须先了解.NET技术出现的原因和它想解决的问题,必须先了解为什么他们需要XML,Web Services 和 SOA。技术人员一般将微软看成一个平台厂商。微软搭建技术平台,而技术人员在这个技术平台之上创建应用系统。从这个角度,.NET也可以如下来定义:.NET是微软的新一代技术平台,为敏捷商务构建互联互通的应用系统,这些系统是基于标准的,联通的,适应变化的,稳定的和高性能的。从技术的角度,一个.NET应用是一个运行于.NET Framework之上的应用程序。(更精确的说,一个.NET应用是一个使用.NET Framework类库来编写,并运行于公共语言运行时Common Language Runtime之上的应用程序。)如果一个应用程序跟.NETFramework无关,它就不能叫做.NET程序。比如,仅仅使用了XML并不就是.NET应用,仅仅使用SOAPSDK调用一个Web Service也不是.NET应用。.NET是基于Windows操作系统运行的操作平台,应用于互联网的分布式。
    2.3 相关技术1.B/S结构
    B/S结构是软件系统体系结构,通过它可以充分利用两端硬件环境的优势,将任务合理分配到Client端和Server端来实现,降低了系统的通讯开销。目前大多数应用软件系统都是Client/Server形式的两层结构,由于现在的软件应用系统正在向分布式的Web应用发展,Web和Client/Server 应用都可以进行同样的业务处理,应用不同的模块共享逻辑组件;因此,内部的和外部的用户都可以访问新的和现有的应用系统,通过现有应用系统中的逻辑可以扩展出新的应用系统。
    根据实际开发的需要,位置信息管理网站设计的开发中选择了B/S结构开发web应用程序,通过浏览器访问服务器的的方式实现远程操作和数据共享。
    2.Ajax技术
    AJAX即“AsynchronousJavaScript and XML”(异步JavaScript与XML技术),指的是一套综合了多项技术的浏览器端网页开发技术。Ajax的概念由杰西·詹姆士·贾瑞特所提出[1]。
    传统的Web应用允许用户端填写表单(form),当提交表单时就向网页服务器发送一个请求。服务器接收并处理传来的表单,然后送回一个新的网页,但这个做法浪费了许多带宽,因为在前后两个页面中的大部分HTML码往往是相同的。由于每次应用的沟通都需要向服务器发送请求,应用的回应时间依赖于服务器的回应时间。这导致了用户界面的回应比本机应用慢得多。
    与此不同,AJAX应用可以仅向服务器发送并取回必须的数据,并在客户端采用JavaScript处理来自服务器的回应。因为在服务器和浏览器之间交换的数据大量减少(大约只有原来的5%)来源请求,服务器回应更快了。同时,很多的处理工作可以在发出请求的客户端机器上完成,因此Web服务器的负荷也减少了。
    类似于DHTML或LAMP,AJAX不是指一种单一的技术,而是有机地利用了一系列相关的技术。虽然其名称包含XML,但实际上数据格式可以由JSON代替,进一步减少数据量,形成所谓的AJAJ。而客户端与服务器也并不需要异步。一些基于AJAX的“派生/合成”式(derivative/composite)的技术也正在出现,如AFLAX。
    第3章 系统需求分析3.1 系统业务描述根据相应的需求及对应功能的完善实现,汽车配件仓储管理网站设计业务流程主要包括以下几个方面:

    一级管理,通过登录界面,判定是一级管理员后,进入一级管理界面。可以查看配件信息,供应商信息,需求商信息,二管理员信息以及二级管理员的操作日志。并且可以通过切换不同的界面,可以对相应的信息进行添加,删除,修改各个部分的信息。在汽车配件信息界面,可以通过链接查看该零件的生产单位等多种操作。
    二级管理员管理,通过登录界面,判定是二级管理员后,进入二级管理界面。二级管理界面可以查询配件信息以及需求商的基本信息。主要进行入库/出库操作而且额外可以添加需求商户。

    至此,本系统业务流程结束。
    3.2 可行性分析可行性分析应从经济可行性,技术可行性和操作可行性三个方面考虑,以下是对本系统具体的可行性分析。
    1.经济可行性
    利用本校现有的校园网、计算机及配套设备就可使用本系统进行配件的信息管理;对系统的使用都是简单的操作;软件系统由本人在指导老师的帮助下完成的,不要任何费用。
    2.技术可行性
    系统采用B/S架构,前台采用html+css+js编码实现页面的可视化与交互性,后端使用C#实现对SQL Server数据库进行数据的存储、修改、删除等操作,该数据库具有方便、灵活的特点,适应该系统的开发。本系统要求的硬件标准不高,一般的硬件设备足够运行系统。
    3.操作可行性
    使用汽车配件仓储管理系统网站,只需要输入网址就能进入登录界面。然后就是管理首页,主要的管理功能是一级管理员对配件信息,供应商信息,需求商信息,二级管理员信息的增、删、改、查以及对操作日志的查看。其次功能是二级管理员的查询,入库、出库操作以及添加新的需求商功能,实用简单。
    综上所述,开发汽车配件仓储管理网站设计在经济上、技术上、操作上都是可行的。
    3.3 功能需求分析汽车配件仓储信息管理网站设计的使用和相关者有:

    一级管理员:一级管理员对配件信息,供应商信息,需求商信息,二级管理员信息的增、删、改、查以及对操作日志的查看。
    二级管理员:二级管理员能够对配件信息进行查询所搜,主要进行入库、出库操作。同时,可以对需求商的信息进行查询,得到是否有需求商正等待发货而进行出库,也可以添加新的需求商用户。

    汽车配件仓储信息管理网站设计应该满足如下功能需求:
    管理员登陆:能让管理员方便的使用密码登陆系统,从而进行一系列的操作。

    一级管理员可以对配件信息进行增、删、改、查,并且由该配件信息获取其生产单位的相关信息。
    一级管理员对供应商的信息管理。
    一级管理员对二级管理员的信息管理。
    一级管理员对需求商的信息管理。
    一级管理员对日志的查看。
    二级管理员进行出库、入库操作。
    二级管理员添加新的需求商。

    3.4 分析模型3.4.1 业务流程图业务流程图(Transaction Flow Diagram, TFD)就是用一些规定的符号及连线来表示某个具体业务处理过程。是一种物理模型。业务流程图主要是描述业务走向,比如说去ATM机取款,首先得打开自助银行的门走进去,然后找到 一台ATM机,再插入卡输入相应的密码,最后钱才能出来被你取走(当然需要余额充足)。利用它可以帮助分析人员找出业务流程中的不合理流向,业务流程图描述的是完整的业务流程,以业务处理过程为中心,一般没有数据的概念。
    1.数据管理
    数据管理业务流程图如图3-1所示:

    2.设备管理
    设备管理业务流程图如图3-2所示:

    3.汽车配件信息查看
    汽车配件信息查看业务流程图如图3-3所示:

    3.4.2 实体模型图(ER图)E-R图也称实体-联系图(EntityRelationship Diagram),提供了表示实体类型、属性和联系的方法,用来描述现实世界的概念模型。
    系统总ER图如图3-4所示:

    管理员实体用来存储管理员的基本信息,其ER图如图3-5所示:

    供应单位实体用来存储供应单位的相关信息,其ER图如图3-6所示:

    需求单位实体用来存储需求单位的相关信息,其ER图如图3-7所示:

    汽车配件实体存储汽车配件的相关信息,其ER图如图3-8所示:

    操作日志实体存储相关操作的信息,其ER图如图3-9所示:

    3.4.3 系统用例图1.总用例图
    总用例图如图3-10所示:

    2.子用例图
    一级管理员配件信息管理如图3-11所示:

    二级管理员配件信息管理如图3-12所示:

    第4章 系统设计4.1概要设计系统的各部分功能模块独立开发、调试,然后利用系统集成的方法将各个模块信息传入数据库。各个功能模块采用事件驱动的方式 与应用程序进行交互,系统部分程序的应用执行是在后台进行的。汽车配件仓储管理网站设计的系统总体结构设计如图4-1所示。

    各模块的结构:
    1.二级管理员管理模块:
    该模块有如下功能:二级管理员可添加需求商信息,并且可以对汽车配件进行出库入库操作。其模块功能图如图4-2所示:

    2.一级管理员管理模块:
    该模块有如下功能:一级管理员可对该模块进行管理,可以查看所有汽车配件,供应商,需求商,管理员的信息,并且可以对信息进行添加,修改,删除,而且可以查看日志信息。其模块功能图如图4-3所示:

    4.2 数据库设计通过以上的对网站的综合分析,本网站选择SQL Server作为网站的数据库,数据库中包括管理员信息、供应商基本信息、需求商基本信息、二级管理员信息、操作日志信息。
    管理员信息表



    表名:Users
    备注:管理员信息表







    字段名称
    数据类型
    自增主键
    允许为空
    默认值


    UserId
    int





    UserName
    nvarchar(50)





    Password
    nvarchar(50)





    Type
    nvarchar(50)





    LoginTime
    Datetime





    Department
    nvarchar(50)





    汽车配件信息表



    表名:CarParts
    备注:汽车配件信息表







    字段名称
    数据类型
    自增主键
    允许为空
    默认值


    PartId
    int





    PartName
    nvarchar(50)





    FromDepartId
    nvarchar(50)





    Num
    nvarchar(50)





    InTime
    Datetime





    UnitPrice
    decimal(18, 2)





    Quantity
    int





    供应单位信息表



    表名: Supplies
    备注:供应商信息表







    字段名称
    数据类型
    自增主键
    允许为空
    默认值


    Id
    int





    Name
    nvarchar(50)





    Principal
    nvarchar(50)





    Address
    nvarchar(50)





    Phone
    nvarchar(50)





    Ways
    nvarchar(50)





    Num
    nvarchar(50)





    供应单位信息表



    表名: NeedMerchant
    备注:需求商信息表







    字段名称
    数据类型
    自增主键
    允许为空
    默认值


    Id
    int





    Num
    nvarchar(50)





    Name
    nvarchar(50)





    Principal
    nvarchar(50)





    Phone
    nvarchar(50)





    IsDeliver
    bit





    Address
    nvarchar(50)





    NeedPartName
    nvarchar(50)





    NeedNum
    int





    操作日志信息表



    表名: Log
    备注:操作日志信息表







    字段名称
    数据类型
    自增主键
    允许为空
    默认值


    Id
    int





    Operator
    nvarchar(50)





    Time
    Datetime





    Details
    Nverchar(1000)





    Type
    nvarchar(50)





    IP
    nvarchar(50)





    4.3 功能模块设计流程图位置信息管理网站设计其功能已详细介绍,本节将详细介绍各个功能模块的设计流程图。其中登录界面程序流程图,如图4-9所示:

    网站中很多功能都涉及到修改数据,修改数据信息主要是实现前端数据与后台数据库的交互,以ID为判断条件将修改后的信息用SQL语句调用数据库修改相应的数据,刷新数据库及相应页面,显示数据库的信息。修改数据流程图如图4-11所示,

    删除数据信息,实现主要是根据删除按钮或是全选删除按钮的点击事件获取相应的要删除信息的ID,然后根据ID利用SQL语句删除数据库中的数据,更新数据库。删除程序主要流程图,如图4-12所示:

    增加数据信息的功能主要是将表单中的数据提交到后台数据库中,在由后台对数据做相应的判断,若不符合要求,则显示增加不成功,不保存到数据库,否则将数据保存到相应数据库。增加数据信息的程序流程图,如图4-14所示:

    4.4 软件界面设计本节将系统展示位置信息管理网站设计完成后页面的完成情况,及大致的布局。其中登录界面如图4-16所示:

    第5章 系统编码与测试5.1 系统编码1.数据库连接代码
    <add name="MainConn"connectionString="server=.;database=CarParts;user=sa;pwd=0301;Max Pool Size=512;" provider Name="System.Data.SqlClient"/>
    2.登录界面核心代码
    <form> <label for="username">用户名</label> <input name="username" type="text" placeholder="请输入用户名" id="name"/> <label for="pass">密码</label> <input name="pass" type="password" placeholder="请输入密码" id="password"/> <input value="登录" id="submit" /></form>
    3.登录后台核心代码
    using System.Data;using System.Data.SqlClient;using System.Linq;using System.Text;using System.Web;using System.Web.SessionState;namespace WebApp{ /// <summary> /// LoginHandler 的摘要说明 /// </summary> public class LoginHandler : IHttpHandler, IRequiresSessionState { public void ProcessRequest(HttpContext context) { context.Response.ContentType = "text/plain"; //获取用户名和密码 string name = context.Request["name"]; string password = context.Request["password"]; //获取数据库数据,放置在ds中 StringBuilder sb = new StringBuilder(); sb.AppendLine("SELECT TOP 1 UserId,UserName,Password,Type,LoginTime,Department "); sb.AppendLine("FROM Users"); sb.AppendLine("WHERE UserName = @UserName AND Password = @Password; "); SqlParameter[] pms = { new SqlParameter("@UserName",SqlDbType.NVarChar,50), new SqlParameter("@Password",SqlDbType.NVarChar,50) }; pms[0].Value = name; pms[1].Value = password; DataSet ds = DbHelperSQL.Query(sb.ToString(),pms); //判断表中是否存在数据,如果有数据则登录成功,如果没有数据,则登录失败 if (ds.Tables[0].Rows.Count > 0) { //根据判断选择进入哪个主页,无论进来的管理员或者是普通用户都记录下操作者的ID,方便记录日志 if (ds.Tables[0].Rows[0]["Type"].ToString() == "1") { context.Session["Id"] = ds.Tables[0].Rows[0]["UserId"].ToString(); context.Session["LoginTime"] = DateTime.Now.ToString(); context.Response.Write("ok1:登录成功"); } else { context.Session["Id"] = ds.Tables[0].Rows[0]["UserId"].ToString(); context.Session["LoginTime"] = DateTime.Now.ToString(); context.Response.Write("ok2:登录成功"); } } //验证失败弹出提示框 else { context.Response.Write("用户名或密码错误,请重新登录"); } } public bool IsReusable { get { return false; } } }}
    4.一级管理员首页界面前端核心代码
    <div class="easyui-layout" style="width:960px; height:800px;margin:50px auto;"> <%--采用easy-ui布局--%> <%--头部导航--%> <div data-options="region:'north',border:false" class="top_bar"> <h1>汽车仓储管理系统</h1> </div> <%--头部导航结束--%> <%--左侧的切换选择--%> <div data-options="region:'west',split:true,title:'信息总览'" class="left_nav"> <ul class="main_nv"> <li class="nv_item"><a href="javascript:void(0)" class="linkToPage" url="InfoPage/PartsInfo.aspx">配件信息</a></li> <li class="nv_item"><a href="javascript:void(0)" class="linkToPage" url="InfoPage/Supplies.aspx">供应商信息</a></li> <li class="nv_item"><a href="javascript:void(0)" class="linkToPage" url="InfoPage/NeedMerchant.aspx">需求商信息</a></li> <li class="nv_item"><a href="javascript:void(0)" class="linkToPage" url="InfoPage/Manager.aspx">管理员信息</a></li> <li class="nv_item"><a href="javascript:void(0)" class="linkToPage" url="InfoPage/Log.aspx">操作日志</a></li> </ul> </div> <%--左侧的切换选择结束--%> <%--底部标注信息--%> <div data-options="region:'south',border:false" class="bottom_bar"> <h3>此系统仅供本公司人员使用</h3> </div> <%--底部标注信息结束--%> <%--核心内容展示区域--%> <div data-options="region:'center',title:'信息管理'" class="right"> <div class="easyui-tabs" style="width:700px;height:250px;" class="right" fit="true" id="tt"> <div title="配件信息" style="padding:10px;overflow:hidden;"class="right"> <iframe src="InfoPage/PartsInfo.aspx" scrolling="no" width="100%" height="100%" frameborder="0"></iframe> </div> </div> </div> <%--核心内容展示区域结束--%></div>
    5.二级管理员首页界面核心代码
    <h1 class="top_title" style="margin-bottom:100px;text-align:center;">汽车配件的出库/入库</h1> <form id="form1" runat="server"> <div> <%--<asp:Button ID="btnAdd" runat="server" Text="添加" />--%><%---------------------------------需求商信息--------------------------------------------------%> <asp:GridView ID="gvNeedMer" runat="server" AutoGenerateColumns="False" DataKeyNames="Id" AllowPaging="True" OnPageIndexChanging="gvParts_PageIndexChanging"> <Columns> <asp:CheckBoxField DataField="IsDeliver" HeaderText="已发货" /> <asp:BoundField DataField="Num" HeaderText="编号" /> <asp:TemplateField HeaderText="单位名称"> <ItemTemplate> <a id="linkToPartInfoEdit" target="_blank" href="javascript:linkToPart(<%# Eval("Id") %>)"> <%#Eval("Name") %> </a> </ItemTemplate> </asp:TemplateField> <asp:BoundField DataField="Principal" HeaderText="负责人" /> <asp:BoundField DataField="Address" HeaderText="地址" /> <asp:BoundField DataField="Phone" HeaderText="联系电话" /> <asp:TemplateField HeaderText="添加"> <ItemTemplate> <input type="button" id="btnAdd" value="添加"/> </ItemTemplate> </asp:TemplateField> </Columns> <%--添加分页--%> <PagerTemplate> 当前第: <%--//((GridView)Container.NamingContainer)就是为了得到当前的控件--%> <asp:Label ID="LabelCurrentPage" runat="server" Text="<%# ((GridView)Container.NamingContainer).PageIndex + 1 %>"></asp:Label> 页/共: <%-- //得到分页页面的总数--%> <asp:Label ID="LabelPageCount" runat="server" Text="<%# ((GridView)Container.NamingContainer).PageCount %>"></asp:Label> 页 <%--//如果该分页是首分页,那么该连接就不会显示了.同时对应了自带识别的命令参数CommandArgument--%> <asp:LinkButton ID="LinkButtonFirstPage" runat="server" CommandArgument="First" CommandName="Page" Visible='<%#((GridView)Container.NamingContainer).PageIndex != 0 %>'>首页</asp:LinkButton> <asp:LinkButton ID="LinkButtonPreviousPage" runat="server" CommandArgument="Prev" CommandName="Page" Visible='<%# ((GridView)Container.NamingContainer).PageIndex != 0 %>'>上一页</asp:LinkButton> <%--//如果该分页是尾页,那么该连接就不会显示了--%> <asp:LinkButton ID="LinkButtonNextPage" runat="server" CommandArgument="Next" CommandName="Page" Visible='<%# ((GridView)Container.NamingContainer).PageIndex != ((GridView)Container.NamingContainer).PageCount - 1 %>'>下一页</asp:LinkButton> <asp:LinkButton ID="LinkButtonLastPage" runat="server" CommandArgument="Last" CommandName="Page" Visible='<%# ((GridView)Container.NamingContainer).PageIndex != ((GridView)Container.NamingContainer).PageCount - 1 %>'>尾页</asp:LinkButton> 转到第 <asp:TextBox ID="txtNewPageIndex" runat="server" Width="20px" Text='<%# ((GridView)Container.Parent.Parent).PageIndex + 1 %>'/>页 <%--//这里将CommandArgument即使点击该按钮e.newIndex 值为3--%> <asp:LinkButton ID="btnGo" runat="server" CausesValidation="False" CommandArgument="-2" CommandName="Page" Text="Go"/> </PagerTemplate> </asp:GridView><%---------------------------------需求商信息结束--------------------------------------------------%><%---------------------------------配件信息表--------------------------------------------------%> <div class="input_info" style="width:620px;margin:15px auto;color:white;"> <span>操作类型:</span> <asp:DropDownList ID="ddlType" runat="server"> <asp:ListItem>入库</asp:ListItem> <asp:ListItem>出库</asp:ListItem> </asp:DropDownList> <span>编号:</span> <asp:TextBox runat="server" ID="txtNum"></asp:TextBox> <span>数量:</span> <asp:TextBox runat="server" ID="txtQuantity"></asp:TextBox> <div class="direction"> <span>需求商编号:</span> <asp:TextBox runat="server" ID="txtNeedMerchant"></asp:TextBox> </div> <asp:Button runat="server" Text="确认" ID="btnConfirm" OnClick="btnConfirm_Click"></asp:Button> </div> <asp:GridView ID="gvParts" runat="server" DataKeyNames="PartId" AutoGenerateColumns="False" AllowPaging="True" OnPageIndexChanging="gvParts_PageIndexChanging"> <Columns> <asp:BoundField DataField="Num" HeaderText="编号" /> <asp:BoundField DataField="PartName" HeaderText="名称" /> <asp:BoundField DataField="Name" HeaderText="生产单位" /> <asp:BoundField DataField="InTime" HeaderText="入库时间" /> <asp:BoundField DataField="Quantity" HeaderText="数量" /> <asp:BoundField DataField="UnitPrice" HeaderText="单价" /> </Columns> <%--添加分页--%> <PagerTemplate> 当前第: <%--//((GridView)Container.NamingContainer)就是为了得到当前的控件--%> <asp:Label ID="LabelCurrentPage" runat="server" Text="<%# ((GridView)Container.NamingContainer).PageIndex + 1 %>"></asp:Label> 页/共: <%-- //得到分页页面的总数--%> <asp:Label ID="LabelPageCount" runat="server" Text="<%# ((GridView)Container.NamingContainer).PageCount %>"></asp:Label> 页 <%--//如果该分页是首分页,那么该连接就不会显示了.同时对应了自带识别的命令参数CommandArgument--%> <asp:LinkButton ID="LinkButtonFirstPage" runat="server" CommandArgument="First" CommandName="Page" Visible='<%#((GridView)Container.NamingContainer).PageIndex != 0 %>'>首页</asp:LinkButton> <asp:LinkButton ID="LinkButtonPreviousPage" runat="server" CommandArgument="Prev" CommandName="Page" Visible='<%# ((GridView)Container.NamingContainer).PageIndex != 0 %>'>上一页</asp:LinkButton> <%--//如果该分页是尾页,那么该连接就不会显示了--%> <asp:LinkButton ID="LinkButtonNextPage" runat="server" CommandArgument="Next" CommandName="Page" Visible='<%# ((GridView)Container.NamingContainer).PageIndex != ((GridView)Container.NamingContainer).PageCount - 1 %>'>下一页</asp:LinkButton> <asp:LinkButton ID="LinkButtonLastPage" runat="server" CommandArgument="Last" CommandName="Page" Visible='<%# ((GridView)Container.NamingContainer).PageIndex != ((GridView)Container.NamingContainer).PageCount - 1 %>'>尾页</asp:LinkButton> 转到第 <asp:TextBox ID="txtNewPageIndex" runat="server" Width="20px" Text='<%# ((GridView)Container.Parent.Parent).PageIndex + 1 %>'/>页 <%--//这里将CommandArgument即使点击该按钮e.newIndex 值为3--%> <asp:LinkButton ID="btnGo" runat="server" CausesValidation="False" CommandArgument="-2" CommandName="Page" Text="Go"/> </PagerTemplate> </asp:GridView> </div></form>
    6.二级管理员首页后台核心代码
    protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { BindData(); } } //绑定gvParts和gvNeed public void BindData() { //查询数据 StringBuilder sb = new StringBuilder(); sb.AppendLine("SELECT C.PartId,C.Num,C.PartName,S.Name,C.InTime,C.Quantity,C.UnitPrice"); sb.AppendLine("FROM CarParts AS C INNER JOIN Supplies AS S"); sb.AppendLine("ON C.FromDepartId = S.Id;"); DataSet ds = DbHelperSQL.Query(sb.ToString()); //将数据绑定到gvParts上展示 gvParts.DataSource = ds.Tables[0]; gvParts.DataBind(); //查询数据并绑定显示,绑定需求商表 string sql = "SELECT Id,IsDeliver,Num,Name,Principal,Address,Phone FROM NeedMerchant;"; DataSet dsNeed = DbHelperSQL.Query(sql); gvNeedMer.DataSource = dsNeed; gvNeedMer.DataBind(); } //分页 protected void gvParts_PageIndexChanging(object sender, GridViewPageEventArgs e) { // 得到该控件 GridView theGrid = sender as GridView; int newPageIndex = 0; if (e.NewPageIndex == -3) { //点击了Go按钮 TextBox txtNewPageIndex = null; //GridView较DataGrid提供了更多的API,获取分页块可以使用BottomPagerRow 或者TopPagerRow,当然还增加了HeaderRow和FooterRow GridViewRow pagerRow = theGrid.BottomPagerRow; if (pagerRow != null) { //得到text控件 txtNewPageIndex = pagerRow.FindControl("txtNewPageIndex") as TextBox; } if (txtNewPageIndex != null) { //得到索引 newPageIndex = int.Parse(txtNewPageIndex.Text) - 1; } } else { //点击了其他的按钮 newPageIndex = e.NewPageIndex; } //防止新索引溢出 newPageIndex = newPageIndex < 0 ? 0 : newPageIndex; newPageIndex = newPageIndex >= theGrid.PageCount ? theGrid.PageCount - 1 : newPageIndex; //得到新的值 theGrid.PageIndex = newPageIndex; //重新绑定 BindData(); } protected void btnConfirm_Click(object sender, EventArgs e) { //为操作日志做好准备 //获取输入编号数量和出库方向 string Num = txtNum.Text; int Quantity = Convert.ToInt32(txtQuantity.Text); string NeederNum = txtNeedMerchant.Text; //根据写入的编号获取配件的名称 string sqlGetPName = "Select PartName from CarParts where Num = @Num;"; SqlParameter[] pmsGetPName = { new SqlParameter("@Num",SqlDbType.NVarChar,50), }; pmsGetPName[0].Value = Num; DataSet dsGetPName = DbHelperSQL.Query(sqlGetPName, pmsGetPName); string pName = dsGetPName.Tables[0].Rows[0]["PartName"].ToString(); //根据写入的编号获取需求商的名称 string nName = ""; if (NeederNum != "") { string sqlGetNName = "Select Name from NeedMerchant where Num = @Num;"; SqlParameter[] pmsGetNName = { new SqlParameter("@Num",SqlDbType.NVarChar,50), }; pmsGetNName[0].Value = NeederNum; DataSet dsGetNName = DbHelperSQL.Query(sqlGetNName, pmsGetNName); nName = dsGetNName.Tables[0].Rows[0]["Name"].ToString(); } //获取用户登录的Id,并获取登录用户的相关信息 int id = Convert.ToInt32(Session["Id"]); DateTime date = Convert.ToDateTime(Session["LoginTime"]); if (id == 0) { id = 2; } string sqlUsers = "Select UserName,Department from Users where UserId = @id;"; SqlParameter[] pms = { new SqlParameter("@id",SqlDbType.Int), }; pms[0].Value = id; DataSet ds = DbHelperSQL.Query(sqlUsers,pms); string type = ddlType.SelectedValue; //插入日志列表 string sqlInsertLog = "insert into Log(Operator,Department,Type,Details,Time,IP)" + " Values(@Operator,@Department,@Type,@Details,getDate(),@IP)"; SqlParameter[] pmsInsertLog = { new SqlParameter("@Operator",SqlDbType.NVarChar,50), new SqlParameter("@Department",SqlDbType.NVarChar,50), new SqlParameter("@Type",SqlDbType.NVarChar,50), new SqlParameter("@Details",SqlDbType.NVarChar,50), new SqlParameter("@IP",SqlDbType.NVarChar,50) }; pmsInsertLog[0].Value = ds.Tables[0].Rows[0]["UserName"]; pmsInsertLog[1].Value = ds.Tables[0].Rows[0]["Department"]; pmsInsertLog[2].Value = type; if (type == "入库") { pmsInsertLog[3].Value = Num + pName + type + Quantity + "件"; } else { pmsInsertLog[3].Value = Num + pName + type + "至" + nName + Quantity + "件"; } pmsInsertLog[4].Value = GetIP(); DbHelperSQL.ExecuteSql(sqlInsertLog,pmsInsertLog); //根据下拉选择不同,有出库和入库两个选择,然后进行不同的操作 //入库操作 if (ddlType.SelectedValue == "入库") { //更改数据库数据 string sql = "update CarParts set Quantity = Quantity + @Quantity where Num = @Num"; SqlParameter[] pms2 = { new SqlParameter("@Quantity",SqlDbType.Int), new SqlParameter("@Num",SqlDbType.NVarChar,50) }; pms2[0].Value = Quantity; pms2[1].Value = Num; DbHelperSQL.ExecuteSql(sql,pms2); //插入操作日志 Response.Redirect("Index2.aspx"); } //出库操作 else { string sql = "update CarParts set Quantity = Quantity - @Quantity where Num = @Num"; SqlParameter[] pms1 = { new SqlParameter("@Quantity",SqlDbType.Int), new SqlParameter("@Num",SqlDbType.NVarChar,50) }; pms1[0].Value = Quantity; pms1[1].Value = Num; DbHelperSQL.ExecuteSql(sql, pms1); //更改需求商的是否发货状态 string sqlUpdate = "update NeedMerchant set IsDeliver = 'True' where Num = @NeedNum;"; SqlParameter[] pars = { new SqlParameter("@NeedNum",SqlDbType.NVarChar,50) }; pars[0].Value = NeederNum; DbHelperSQL.ExecuteSql(sqlUpdate,pars); Response.Redirect("Index2.aspx"); } } //获取Ip public string GetIP() { string result = String.Empty; result = HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"]; if (string.IsNullOrEmpty(result)) { result = HttpContext.Current.Request.ServerVariables["REMOTE_ADDR"]; } if (string.IsNullOrEmpty(result)) { result = HttpContext.Current.Request.UserHostAddress; } if (string.IsNullOrEmpty(result)) { return "127.0.0.1"; } return result; }
    5.2 系统测试为了保证设计完成后的软件是有效的,健壮的,在软件开发的生命周期中要对软件进行测试,测试工作可以验证软件的需求是否都得以实现,软件是否正确地提供了需要的功能,以及软件是否能健壮稳定地运行。所以,本网站的测试主要以如下几方面为切入点:

    一、功能验收测试:对应软件的需求分析和详细设计文档,检查系统所应该实现的功能是否已经实现。对于一个软件,完成并能够使用的最基本条件是所有的功能都能覆盖,并且功能能够成功执行。 二、集成验收测试:在保证了每个功能都实现并可用之后,需要验证系统的每个功能的正确性,于是进一步的测试则要保证每一个功能的每种可能执行方式 都能得到正确的结果。这样我们才可以说,对外发布的系统是一个正确的系统。 三、健壮性、稳定性及性能测试:为了让系统能够稳定的高效运行,需要对系统进行以下的测试。如,选择一些非正常的输入,这时系统需要能继续稳定运 行,或者很容易从错误中自动恢复;当长时间的进行持续的操作时,系统对资源 的消耗应该处于稳定的,可接受的范围内,尤其要避免内存泄露等问题带来的风险;系统还要达到需求分析文档中所规定的性能指标,所以还要针对实际情况进 行性能测试;最后系统还要根据需要对不同平台、不同环境的兼容性进行测试。 整个系统需要测试的模块主要有登录模块,一级管理员的管理模块,二级管理员的管理模块。
    系统测试:功能测试、性能测试、验收测试。目的是为了保证所实现的系统确实是用户所需要的。
    登录验证,当用户名或密码不输入时提示用户重新输入,以及当用户输入的用户名和密码不匹配时,提示重新输入。当输入的是一级管理员则进入一级管理员首页,当输入的是二级管理员进入二级管理员首页。

    添加配件

    点击配件名称,弹出编辑界面

    勾选点击删除按钮便可以对信息进行删除

    填写入库信息后点击确定,该汽车配件数量增加
    参考文献[1] Karli Watson著,齐立波译,C#入门经典(第6版),2014-8
    [2] (美)内格尔(Nagel.C)等所著,C#高级编程,2008-10-1
    [3] 明日科技 著,ASP.NET从入门到精通,2012-09
    [4] 王珊,萨师煊.数据库系统概论(第四版).高等教育出版社,2006.5
    [5] ASP.NET 入门经典(第9版),2016-11
    [6] Baron Scbwartz.高性能MySQL(第3版)[M].电子工业出版社,2013
    [7] (美)加洛韦 等著,ASP.NET MVC5高级编程(第5版),2015-02
    [8] 构建之法 现代软件工程(第二版),2015-07
    [9] 王国辉,王毅.数据库系统开发案例精选[M].人民邮电出版社,2006
    [10] 软件工程:实践者的研究方法(原书第8版),2016-11
    1 评论 8 下载 2018-09-26 18:38:29 下载需要21点积分
  • 基于C语言的外卖管理系统

    一. 设计目的此次课设我的主题是外卖管理系统,则希望可以模拟网上订餐,店家工作,专人管理的过程。并实现注册与登录以及基本信息的输出。
    二. 设计内容系统分为三端登录,分别为管理员,用户以及店主;两端注册,分别为用户以及店铺,其中店铺的注册成功需要得到管理员的审核。
    管理员可以实现店铺的增删改查以及自己信息的查看修改和处理,其中,密码修改需要输入旧的密码,三次错误可以找回密码,输入手机号,若手机号匹配得当,则可产生三位数的随机验证码,输入验证码可修改密码。用户可以实现菜品查看与购买,订单查看以及修改,(但对于已超过三分钟的订单不能修改,因为已经配送),本人信息查看以及修改,若查看订单时统计总消费为0,则提示去购买菜品。店家可以实现菜品增加删除查看,业绩的查看,业绩为0时可提示自我反思和提升。
    在本系统中,限制了用户的余额,当购买时余额不足时,需要进行账户的充值,而购买结束后,账户的余额也会相应的减少,这也正是本系统的重要部分,因为网上订餐主要就是体现在其的购买上。
    三.概要设计
    管理员端可以查看店铺信息,店铺信息修改(分为增删改查),处理申请店铺,和修改本人信息
    用户端分为用户相关(为用户本人对自己信息的操作),查看订单,查看菜品,购买菜品,以及订单排序
    管理员端为增添菜品,删除菜品,修改菜品,查看菜品以及业绩查看(统计总订单与总收入)
    注册分为用户注册和点击注册,其中店家注册需要的到管理员的认证,认证通过之后才可登录

    3.1 功能模块图
    3.2 各个模块详细的功能描述
    管理员登录:管理员可以查看店铺信息,处理店铺(包括店铺的增加删除和录入),认证店铺(认证申请的店铺),查看本人信息,修改本人信息(分为修改电话,修改地址,修改密码,其中修改密码需输入就得密码,当时那次输入不正确的时候可以找回密码)
    用户登录:用户可以查看所有的菜品,可以够买菜品(购买时字需要输入菜的种类或者菜名就可以搜索到相应的菜,购买之后需扣除余额,余额不足时会提示购买失败,以及月充值)。查看订单,即统计输出所有的订单,当没有订单时会提示去购买。订单排序分为按才菜名升序以及按总价降序
    店家登录:可以查看本家的菜品,可以增加或者删除本家的菜品,可以修改菜品的名称,单价以及菜系,查看业绩,即统计输出用户在本店产生的所有的订单
    注册:分为用户注册以及店家注册,其中用户注册时当用户名有重复时提示重新输入,当设置的密码不足8位时提示可以修改或者放弃修改,输入完基本信息之后需要绑定银行卡和设置支付密码,当银行卡不足15位时提示重新输入。注册成功之后即可返回登录。店家输入完基本信息之后,需要得到管理员的审核,当审核成功之后才可登录

    四、详细设计4.1 功能函数的调用关系图
    4.2 各功能函数的数据流程图4.2.1 用户申请
    4.2.2 店铺删除
    4.2.3 菜品查找
    4.2.4 店铺订单统计
    4.3 重点设计及编码4.3.1 找回密码修改密码连续三次输错旧密码可以找回密码,输入绑定的电话号正确即可发送验证码到文件,输入正确的验证码即可重新输入新密码。

    4.3.2 购买时余额不足提示充值
    4.3.3 通过调用时间函数修改订单的数量时有时间限制,超过三分钟即提示订单已经配送不可修改,为超过三分钟则提示尽快修改
    五、测试数据及运行结果5.1 正常测试数据和运行结果5.1.1 输出店铺
    5.1.2 删除店铺
    5.1.3 输出并统计店铺订单
    5.1.4 查看本家店铺菜品
    5.1.5 添加菜品
    5.2 异常测试数据及运行结果5.2.1 注册用户
    5.2.2 购买菜品余额不足时
    2 评论 85 下载 2018-10-21 15:23:43 下载需要13点积分
  • 基于Qt实现的简单CPU模拟器

    一、设计内容简单CPU模拟器的设计与实现
    二、设计要求
    可以读取TXT格式的文件(文件内容为汇编指令)在界面中显示
    可以将TXT文件中的汇编指令(规定格式)进行编译,显示在界面中
    根据编译后的内容,执行相关操作,结果显示在界面中
    对相关操作进行描述输出在界面中

    三、设计过程3.1 开发环境的选择要实现界面的设计需要GUI编程,由于对visual c++的不熟悉,故选择了支持C++ 的Qt。
    Qt 是一个1991年由Qt Company开发的跨平台C++应用程序开发框架。它既可以开发GUI程序,也可用于开发非GUI程序,比如控制台工具和服务器。Qt是面向对象的框架,使用特殊的代码生成扩展(称为元对象编译器(Meta Object Compiler, moc))以及一些宏,Qt很容易扩展,并且允许真正地组件编程。

    3.2 UI的布局和设计结合设计要求方案,需要几个按键分别实现 读取文件,编译文件,生成TXT文件,运行 等操作。 同时需要几个文本显示区,用来显示相关内容。 以及在设计过程中给User的一些提示等。
    根据以上分析,设计了一个ui界面:

    完成了初步的搭建后,进行相关的布局,布局在Qt可以通过代码实现或者直接手动操作,这里我选择了后者,通过在合适位置加入VerticalSpacer和HorizontalSpacer (垂直和水平弹簧),最后按Ctrl + G,实现栅格布局。
    同时为了方便使用,需要添加相关的提示,这里我选择了使用QLineEdit控件和QLabel控件,通过添加代码实现内容的显示。
    代码部分
    在构造函数中书写相关内容。
    构造函数
    Widget::Widget(QWidget *parent) : QWidget(parent),ui(new Ui::Widget){}内容举例
    ui->label->setText(“注意:按完 [编译生成TXT] 后,再按 [运行]”);

    3.3 读取文件的实现课设过程要求对文件进行读取操作。因此要实现按键按下后对文件的选择,以及读取。
    通过向槽函数中写入相关操作,实现操作与按键的关联,从UI中选择相应的Push_button,右键选择转到槽函数,选择的信号click()。
    void Widget::on_buttonRead_clicked();//槽函数的声明部分
    槽函数内部代码具体思路:
    QString str = QFileDialog::getOpenFileName(this,"选择要打开的txt文本","../","fileName(*.txt)"); //获取文件路径QFile file(str); //创建文件对象,关联路径file.open(QIODevice::ReadOnly) //打开文件QByteArray array = file.readAll(); //读取文件内容ui->textEdit->setText(QString(array)); //将内容设置到UI的TextEdit中显示
    文件打开

    文件内容读取

    3.4 编译过程的实现根据文件内容,将其中的汇编指令编译成相关的二进制数。分析后发现这是对字符串的处理。我的思路是,一次读取文件的一行,对字符串进行拆分,拆分过程中用到的函数为:
    left() //从字符串左边获取参数长度的子串right() //从字符串右边获取参数长度的子串mid() //从字符串的某一位置(第一个参数)开始获取参数长度(第二个参数)的子串
    除去拆分字符串,还要进行字符串匹配,匹配相关的关键字,这里使用:
    contains() //根据括号中是否有相关内容,返回一个bool值
    同时还需要将具体的数字转换为需要的二进制数字,这里使用:
    //进制转换函数QString::number(str_behind.toInt(&ok,10),2)//将字符串中内容转换为10进制整型str_behind.toInt(&ok,10)//替换函数replace()//字符串组合后输出QString final_Str = "1001 0010" + binaryStr + Str_x + "\n";
    注:这里的代码写在与Push_button相关联的槽函数中

    3.5 编译生成TXT这一步是为了运行过程中读取内容所设置的,String一行一行读取的相关函数未找到,于是想到了将内容写入一个文本文件中,点击运行按钮后读取文件内容。
    这一步的关键操作是创建文件,同时写入内容。
    代码实现:(由于这一部分简短且有代表性,我将全部代码展示出来并解释)
    void Widget::on_CreatTxtButton_clicked(){ //创建一个文件并写入TxtEdit_2中的内容 QFile file2("PYP的编译后文件.txt"); if(file2.exists()) { file2.open(QIODevice::WriteOnly); QString str = ui->textEdit_2->toPlainText(); file2.write(str.toUtf8()); } else { file2.open(QIODevice::WriteOnly); QString str = ui->textEdit_2->toPlainText(); file2.write(str.toUtf8()); } file2.close();}
    这里的打开方式为QIODevice::WriteOnly,这种打开方式下,如果文件不存在会创建一个,这也是我们的目的所在——-创建一个文件。
    通过toPlainText()函数实现获取编译后显示在UI界面中的textEdit_2的文本。将其通过write函数写入创建的TXT文件中。

    3.6 运行这是最核心的一部分。主要方面有,对字符串的处理,相关指令的理解与执行,运行结果的组包输出(更直观的看到运行效果)。
    3.6.1 对字符串的处理这部分更多的是借鉴编译过程的方法和技巧,同过拆分字符串,使用相关的函数来实现替换,进制转换,数据类型转换等。
    这里我选择一个Ldi指令的运行来说明一下,结合注释看代码。
    //确定是否为Ldi指令 if(str.left(5).contains("1110")) { bool ok; //截取关键的内容,注意MID()方法的参数,截取寄存器名字,截取数值 QString numstr = str.mid(5,4) + str.mid(15,4); QString RegisterStr = str.mid(10,4); //通过for语句遍历是哪一个寄存器 for(int i=0;i<16;i++) { if(i == RegisterStr.toInt(&ok,2)) { int result = Rarray[i].theNumberInRegister = numstr.toInt(&ok,2); //用于组包的字符串 QString Str_combination; //组包 Str_combination = QString("寄存器 R%1 被装入值:").arg(i); Str_combination = timeStr + " " + Str_combination + QString::number(result,16).toUpper() + "\n"+ "\n"; qDebug()<< Str_combination; //整合到最终的输出字符串中,设置为文本 Final_Str += Str_combination; ui->textEdit_3->setText(Final_Str); } } }
    3.6.2 对指令的理解与执行这次使用到的指令有:

    Add Rd, Rr 加法指令
    Sub Rd, Rr减法指令
    Mul Rd, Rr无符号乘法指令
    RJMP K 无条件相对跳转指令
    BRMI K 有条件相对跳转指令
    Mov Rd, Rr数据传送指令
    LdiRd, K载入立即数指令
    LdRd, X装载指令
    StX, Rr 存储指令
    Nop空操作指令

    几个运算指令中需要注意的是Mul无符号乘法指令,这个指令执行后,将相乘的两个寄存器的内容的高8位存储到R1中,低8位存储到R0中,这个在操作过程中需要特别注意。
    RJMP和BRMI指令要根据K对应的十进制数字进行相应的PC增加,增加后,对应于PC的全局变量要实现增加。
    Ld,St指令的执行类似于存储器间接寻址,是将一个存储器中的数作为一个地址,去讲这个地址对应的存储器的内容进行装载。
    3.6.3 运行结果的组包和输出这里在6.1中的代码中可以看到相关的操作,我举出所用的函数:
    QString i; // current file's numberQString total; // number of files to processQString fileName; // current file's nameQString status = QString("Processing file %1 of %2: %3").arg(i).arg(total).arg(fileName);First, arg(i) replaces %1. Then arg(total) replaces %2. Finally, arg(fileName) replaces %3.
    这是Qt帮助文档的内容,十分直观,通过设置’%1’,’%2’等,让arg()函数中的内容去替换,有点类似C语言中的Printf()函数,使用它可以让内容更直观的动态的现实出来。

    3.7 最终结果的显示所有指令运行完毕后,要有一个最终结果,显示每个寄存器中的值。
    首先展示一下,我的寄存器的数据结构:
    在寄存器类中,包含两个关键成员,寄存器名字和寄存器中的数。
    int register_Name; //寄存器的名字int theNumberInRegister; //寄存器中存储的内容
    同时在Widget.h的头文件中,声明关键的成员变量,一个寄存器数组,一个间接寻址寄存器数,间接寻址寄存器数组的索引。
    //创建一个Register类的对象数组Register Rarray[16];//创建一个Register类的对象数组,用于间接寻址产生的问题Register RegisterIndirect[5];//记录间接寻址使用的寄存器的索引static int indexOfRegisterIndirect;
    通过遍历两个数组来实现内容的显示。
    举例代码:
    for(int i=0;i<16;i++){ 16).toUpper(); Str_over += QString("R%1:").arg(i) + QString::number(Rarray[i].theNumberInRegister,16).toUpper() + "\n"; ui->textEdit_4->setText(Str_over);}
    遍历每一个寄存器,输出其中的值,同时进行相关的进制,大小写转换,最后输出到TextEdit_4中。
    3.8 生成相关的EXE和库文件在没有Qt的电脑上,相关的EXE文件是运行不了的。这里的解决方法是:
    使用Release模式运行程序,生成相关的EXE文件,将EXE文件,放到一个单独的文件夹中,使用Qt自己的命令提示符,进入EXE文件的所在的目录中,执行指令windeployqt + exe文件名字,自动加入相关的库文件到此文件夹中。

    四、设计中遇到的问题和解决方案4.1 问题
    如何搭建一个界面
    如何截取字符串
    如何替换字符串
    如何读取文件
    如何写入文件
    对指令的理解
    运行过程的现实
    最终结果的展示
    可执行程序在别的电脑上的运行

    4.2 解决方案看视频学习Qt的使用,同时多练习多敲,结合Qt的帮助文档。分析字符串的位置,相关截取函数的查找,对应函数的使用方法。不断的敲代码,不断的调试,多做记录。与同学讨论,咨询指令的具体执行过程,加深对指令的理解。
    五、设计感触通过这次的计算机原理课程设计,我遇到了很多问题,大的,小的都有。代码从最初的起步到慢慢增加,再到发现有大的问题,推倒重来。经验在不断的积累,为最后的实现打下了基础。
    同时,我对计算机原理寄存器部分的理解,相关指令的理解,有了加深,对我所学编程的语言有了更深刻的认识。
    除了在相关学科上有了学习与进步,我在做课设的过程中,更体会到了坚持的重要性,学习,生活都会出现困难,我们应客观认识到这个现实,认识到人的学习,发展的客观过程是有阶段性的。认清自我,帮助自己更好的学习,发展。
    最后,很开心参加这次课程设计,我会在今后的学习生活中,不断的积累自己的知识,提升自己的个人能力。
    0 评论 0 下载 2019-07-01 11:42:11 下载需要10点积分
  • 基于C#实现的进制计算器

    一、实验背景该实验为计算机原理模拟实验平台。在学习计算机原理实验课程中,为了方便同学们学习了解模型机中各种寄存器结构、工作原理、算术、逻辑运算单元及其控制方法。所以我们设计计算机组成原理虚拟实验系统方便同学们更加深入的了解实验内容,掌握计算机通过指令系统进行运算的过程。
    程序编写语言:c# 平台环境:VisualStudio2017 操作系统要求:Windows 7。
    二、设计思想运算器由算术逻辑单元、累加器、状态寄存器、通用寄存器组等组成。算术逻辑运算单元(ALU)的基本功能为加、减、乘、除四则运算,与、或、非、异或等逻辑操作,以及移位、求补等操作。计算机运行时,运算器的操作和操作种类由控制器决定。运算器处理的数据来自存储器,现代计算机都是以存储器为核心,存储能力严重关系到计算机的性能,现代计算机的多级缓存,甚至带有缓存的硬盘都是为了解决性价比问题。由于计算机程序的局部性原理是的Cache-主存结构得以建立,部分程序备份到Cache中,使其读写时大概率命中Cache,极大降低了cpu的等待时长,而且因为Cache比较小,成本得以控制。另外虚拟存储器技术也是为了解决容量和价格的矛盾。Cache-主存的地址映射机制中,全相联方式更加的灵活利用率也高,但是由于采用了全部比较的方式,使得电路复杂,成本加大,而且不利于大容量存储器使用,而直接映射方式采用对号入座的方式简单快速,;处理后的结果数据通常送回存储器,或暂时寄存在运算器中。与Control Unit共同组成了CPU的核心部分。
    三、实验简介根据COP2000实验平台可以进行的实验,以程序的形式体现出来。本实验可进行以下操作:

    A,W寄存器:

    数据是在放开CLOCK键后改变的,也就是CLOCK的上升沿数据被打入WEN,AEN为高时,即使CLOCK有上升沿,寄存器的数据也不会改变
    运算器

    运算器在加上控制信号及数据(A,W)后, 立刻给出结果实现以下实验:加法器,减法器、乘法器等

    四、运算器4.1 流程图和结构图流程图

    结构图

    4.2 运算器原理与分析
    五、运行结果界面
    六、遇到的问题及解决6.1 连线错误实验要完整无错误的进行,首先要确保实验接线图连线的正确性才能确保在进行实验时数据通路流向以及数据的的正确性,这样才能到达实验的目的;在进行实验过程中需要理解每一步骤的原因,也加强自己的理解性和掌握程度;在实验过程中活树会遇到线路正确但数据错误,这能很有可能是自己连接线路有问题,所以在连接线路上一定要保证每条线是否正确。
    6.2 不能在正确的位置进行数据存储并显示开始存入的数据和位置都是对的,但是下面的数据信息是对的但是显示位置不对,后来通过仔细阅读试验资料和向同学请教,终于知道了,在存储完一个数据后要将控制显示位置的按钮进行调试,然后在进行输入就可以了。
    七、心得体会在本次实验做的是运算器实验,主要完成逻辑运算、移位运算及算术运算。这次的实验不是很难,做起来很轻松。当然,在上课前预习很重要,要明白每个部件的含义及作用,这样在实验过程中条理会清晰些。还有,在进行实验前,必须将老师上课所讲的知识重新结合课本,搞明白实验目的和实验原理。 更清晰的明白了计算机的一些数据处理的方法,对计算机的原理产生了更加浓厚的兴趣。知道运算器的输出跟数据总线相连,同时两个输入端通过两个锁存器也与数据总线相连。同时,数据显示灯连接上数据总线,用来显示数据总线的内容。本次实验,提高了我对组成原理实验的积极性,更教育了我实验要认真,要培养了我实验要认真,要严谨的态度,课本知识运用到实践之中,也提高了对课程学习的热情
    0 评论 0 下载 2019-07-01 11:31:38 下载需要6点积分
  • 基于C++实现的简单CPU模拟器

    一、实验背景该实验为计算机原理模拟实验平台。在学习计算机原理实验课程中,为了方便同学们学习了解模型机中各种寄存器结构、工作原理、算术、逻辑运算单元及其控制方法。所以我们设计计算机组成原理虚拟实验系统方便同学们更加深入的了解实验内容,掌握计算机通过指令系统进行运算的过程。
    程序编写语言:c++ 平台环境:VisualStudio2017、C-free
    二、设计思想运算器由算术逻辑单元、累加器、状态寄存器、通用寄存器组等组成。算术逻辑运算单元(ALU)的基本功能为加、减、乘、除四则运算,与、或、非、异或等逻辑操作,以及移位、求补等操作。计算机运行时,运算器的操作和操作种类由控制器决定。运算器处理的数据来自存储器,现代计算机都是以存储器为核心,存储能力严重关系到计算机的性能,现代计算机的多级缓存,甚至带有缓存的硬盘都是为了解决性价比问题。由于计算机程序的局部性原理是的Cache-主存结构得以建立,部分程序备份到Cache中,使其读写时大概率命中Cache,极大降低了cpu的等待时长,而且因为Cache比较小,成本得以控制。另外虚拟存储器技术也是为了解决容量和价格的矛盾。Cache-主存的地址映射机制中,全相联方式更加的灵活利用率也高,但是由于采用了全部比较的方式,使得电路复杂,成本加大,而且不利于大容量存储器使用,而直接映射方式采用对号入座的方式简单快速,;处理后的结果数据通常送回存储器,或暂时寄存在运算器中。与Control Unit共同组成了CPU的核心部分。
    三、试验简介3.1 设计要求将指令存储器和数据存储器分开,指令存储器的地址总线和数据总线宽度均为16位,数据存储器的地址总线宽度为16位,数据总线宽度为8位。
    CPU使用流水线技术,流水级数为5级,分别是:取指、译码、执行、访存、写回。
    输入要求:模拟器从文件test.data读入汇编执行,先将汇编编译成二进制。
    输出要求:模拟器用txt文件记录每一个周期CPU主要寄存器的值,总线数值,程序执行完毕后,用txt文件记录数据存储器的内容。记录数据时要注意对齐。同时界面显示。
    3.2 系统要求
    简单方案:存储器读写操作只需要一个周期,不需要考虑控制信号;指令串行
    中等方案:将指令分解成微指令
    高级方案,存储器读写操作采用5个周期,考虑指令流水

    CPU的主要寄存器及编址方式:

    PC寄存器,复位时的值为0,16位宽
    16个通用寄存器(r0~r15),对应地址为0~15,复位时的值为0,8位宽
    一个程序状态寄存器(sr,对应地址为16),8位宽
    通用寄存器、程序状态寄存器和数据存储器统一编址,通用寄存器既可以用寄存器号访问,也可以用地址空间的地址访问

    程序状态寄存器的格式:



    位位置
    7
    6
    5
    4
    3
    2
    1
    0




    位名称
    I
    T
    H
    S
    V
    N
    Z
    C


    读写属性
    R/W
    R/W
    R/W
    R/W
    R/W
    R/W
    R/W
    R/W


    复位时的值
    0
    0
    0
    0
    0
    0
    0
    0



    程序状态寄存器各位表示的意义:

    I:全局中断允许位:为1时,允许中断,否则,禁止中断,CPU响应中断时,硬件将此位清0,从中断返回时,将此位置1
    T:位复制存储位:BLD指令用此位的值与16个通用寄存器中的某位交换值
    H:半进位标志:即低4位是否向高4位进位或借位,如果有则为1,否则,为0
    S:符号标志位:本位是位N和位V的异或值
    V:有符号数溢出标志位
    C:无符号数溢出标志位
    N:负数标志位:若运算结果是负数,则为1;否则,为0
    Z:0标志位:若运算结果是0,则为1;否则,为0

    指令集:

    Add Rd , Rr 加法指令功能:Rd <- Rd+Rr机器码(二进制表示)0000 1100 dddd rrrr其中,rrrr为源操作数的寄存器号,dddd为目的操作数的寄存器号所影响的标志位 Z,C,N,V,H,S
    Sub Rd , Rr减法指令功能:Rd <- Rd-Rr机器码(二进制表示)0000 1000 dddd rrrr其中,rrrr为源操作数的寄存器号,dddd为目的操作数的寄存器号所影响的标志位Z,C,N,V,H,S
    Mul Rd , Rr无符号乘法指令功能:R1:R0 \<- Rd*Rr机器码(二进制表示)1001 1100 dddd rrrr其中,rrrr为源操作数的寄存器号,dddd为目的操作数的寄存器号所影响的标志位Z,C
    RJMP K 无条件相对跳转指令功能:PC<-PC+K+1机器码(二进制表示)1100 kkkk kkkk kkkk其中, kkkk kkkk kkkk为相对地址所影响的标志位无
    BRMI K 有条件相对跳转指令功能:if (N == 1) PC <- PC + k + 1机器码(二进制表示)1111 0001 kkkk kkkk其中, kkkk kkkk为相对地址所影响的标志位无
    Mov Rd , Rr数据传送指令功能:Rd <-Rr机器码(二进制表示)0010 1100 dddd rrrr其中,rrrrr为源操作数的寄存器号,ddddd为目的操作数的寄存器号所影响的标志位无
    Ldi Rd , K载入立即数指令功能:Rd <-K机器码(二进制表示)1110 KKKK dddd KKKK其中,dddd为目的操作数的寄存器号,目的寄存器只能是r8~r15; KKKK KKKK是立即数。所影响的标志位无
    Ld Rd , X装载指令功能:Rd <-(X)机器码(二进制表示)1001 0000 dddd 1100其中,dddd为目的操作数的寄存器号,X为R14寄存器所影响的标志位无
    St X, Rr存储指令功能 (X) <-Rr机器码(二进制表示)1001 0010 rrrr 1100其中,rrrr源操作数的寄存器号, X为R14:寄存器对所影响的标志位无
    Nop 空操作指令功能 不做任何操作,只消耗CPU时间机器码(二进制表示)0000 0000 0000 0000所影响的标志位无

    四、模拟器流程图

    五、运行结果与界面

    六、遇到的问题及解决打印机器码问题
    比如Ldi指令机器码的打印,1110 KKKK dddd KKKK,dddd是目的寄存器号的二进制,KKKK KKKK是立即数的二进制,而我们读入的是十六进制的字符,遇到第一个问题,是要把十六进制转化为二进制进行打印,但是在打印机器码时立即数的二进制是分开计算的,所以就想到,把立即数字符串拆开成两个字符,存到一个char型数组中,这样在最后打印的时候比较容易,这时引出第二个问题,怎么把一个字符串(十六进制数)拆成一个数组,同时想到,载入立即数指令要把数据存到寄存器中,而我们要想以后使用立即数的计算只能用十进制,其他进制的计算不行。于是我们需要一个数组来存放十进制的立即数,已供以后运算。想到这里开始写了,定义一个int datarray[16][1]的数组来存放立即数的十进制,写一个函数hex_ten(),接收一个字符返回一个整型,要接收字符就要把它分开,写一个做法把字符串分开,存进一个char型数组中,然后把十六进制的字符转化为十进制计算整个立即数的十进制。
    打印加载位置
    如上图的0002、0004,这个加载位置我的理解是一条指令的长度,指令串行,加载完一条指令后,往后移动一条指令的长度。一条指令长度为2。当指令多了后,后面出现000A、000B、001A。所以每一位是十六进制数,只能用一个char型数组存放,数组中的一个元素代表一位数字,涉及到加法运算后我又想到了再设一个int型数组,用于计算。定义了int pctemp[4]={0}。每次只在最后一位运算+2,当pctemp[3]\<16时,打印位置就转为十六进制,一位位的打印,当pctemp[3]>=16了,就要进位了,把pctemp[2]置为1,pctemp[3]-16继续转化为十六进制,再一位位打印,下面具体代码实现。这个代码只能显示16条指令,因为自己写第二次进位的时候当第二次最后一位到了大于等于16的时候,pctemp[2]置为2,然后再次pctemp[3]-16继续转化为十六进制,结果出现了问题,就先搁置了,没有再想。感觉用递归或者别的方法能更简单的达到目的,以后再思考。代码如下:
    char pcchar3[1];char pcchar2[1];if(pctemp[3] < 16){ ten_hex(pctemp[3],pcchar3); cout<<pctemp[0]<<pctemp[1]<<pctemp[2]<<pcchar3; pctemp[3] = pctemp[3]+2;}else if(pctemp[3] >= 16){ ten_hex(pctemp[3]-16,pcchar2); pctemp[2] = 1; cout<<pctemp[0]<<pctemp[1]<<pctemp[2]<<pcchar2; pctemp[3] = pctemp[3]+2;}
    状态寄存器的思考
    只考虑了六个状态位,H,S,V,N,Z,C,因为状态位只有0和1,所以还是用数组来存放状态位,而在这里为了方便用我设置了一个结构体StateR(别名stater)里面定义了一个int型数组array[6],各个状态位除了零标志位Z我很明确的知道,怎么判断,其他的标志位都是一知半解,根据网上搜索到的知识加自己理解写的。

    零标志位:之前存放过各种数据的十进制数,在判断时很轻松。
    负数标志位:按照十进制的结果判断的。
    半进位标志:是低四位向高四位进位和借位的判断,有则置为1。只有Add指令和Sub指令影响到了半进位标志,仔细思考后发现,在执行加法指令时只要两个低四位的十六进制数‘加’在一起大于‘F’,就会向高位进位,而执行减法时,只要被减数低四位的十六进制数小于减数的低四位十六进制数就会向高位借位,之前存放过各种数据的十六进制,也可以判断。
    两个溢出标志位:这里应该会出现问题,我不知道有符号和无符号怎么区分,在写程序的时候是按照没有符号的来写的。不过还是简单写了下,无符号溢出,我根据八位二进制最大表示255,如果计算结果超过了255就算溢出,有符号溢出,我在网上找到相关的知识,根据最高位进位状态⊕次高位进位状态=1,则溢出,根据这个写了段代码,没有去验证。
    符号标志位:根据位N和位V的异或值。

    寄存器内容的打印及数据存储器内容的输出
    R0和R1寄存器存放的是乘积低位和乘积高位,和其他寄存器存放的数据不同,所以分开考虑,在Mul指令中,之前存放了十进制,直接计算,将乘积结果转化为十六进制,用数组接收,R0和R1的数组分别接收十六进制数组的后两位和前两位,这样就实现了乘积高位和乘积低位的存放。
    普通寄存器的输出与R0和R1相比较为简单,在写各条指令的时候,如果涉及到寄存器内容的改变,我都把数据的十进制和十六进制存放起来,在以后输出时直接改变进制输出。
    数据存储器,最早不知道用在哪里,后来看到老师给的供调试的文件里有句话,数据存储器某某单元里存放的是某某数据,然后有了些想法,继续设计结构体,里面两个数组,一个存十进制,一个存十六进制,同时修改,以便以后方便调用。在指令中只有St指令和Ld指令会用到数据存储器,这两条指令在写的时候也遇到麻烦,不明白装载和存储的意义,继续网上搜索,找到了一些知识。St指令:将源寄存器中的内容赋给以R14中的内容为地址 的数据存储器。Ld指令:将R14中的内容做为地址去数据存储器中寻找,把相应数据存储器的内容付给目的寄存器。指令的意义也明白了,代码的实现也就做出来了,最后把各个内容按照进制要求输出就可以了。
    RJMP跳转指令问题
    没有解决,在写跳转指令时只想着怎么跳转,在代码中判断下一条是什么指令然后存放所有字符串的数组的索引直接往后移动相应的位置就能解决跳转问题了,可是最后输出指令机器码的时候,跳转指令的下一条不会输出,仔细看发现,因为跳过去了,所以不会输出,试着加上一些代码,还是没有解决,一旦打印这条指令,涉及到相关寄存器中的内容就会改变。这是最初留下的问题,应该在当时就想清楚,到了后面就无法解决了,最后决定不再打印了,保留问题。
    七、心得体会在本次实验做的是运算器实验,主要完成逻辑运算、移位运算及算术运算。总的来说,这一次的课设是很有挑战性的,学习后是一种体验,但要自己做就又是不一样的感觉了,由刚开始的毫无思路,到一点点的摸索,这其中收获了很多。当然,在上课前预习很重要,要明白每个部件的含义及作用,这样在实验过程中条理会清晰些。还有,在进行实验前,必须将老师上课所讲的知识重新结合课本,搞明白实验目的和实验原理。 更清晰的明白了计算机的一些数据处理的方法,对计算机的原理产生了更加浓厚的兴趣。知道运算器的输出跟数据总线相连,同时两个输入端通过两个锁存器也与数据总线相连。同时,数据显示灯连接上数据总线,用来显示数据总线的内容。本次实验,提高了我对组成原理实验的积极性,更教育了我实验要认真,要培养了我实验要认真,要严谨的态度,课本知识运用到实践之中,也提高了对课程学习的热情
    0 评论 0 下载 2019-07-01 11:16:24 下载需要9点积分
  • 基于C++的公司人员管理系统

    一 系统介绍系统架构如下图所示,主要包括对公司人员信息的查询、修改及存储管理功能。

    二 系统设计创建了两个类来实现程序要求,1为People类,定义了一个人员的各种信息,2为Main类,定义了各种操作函数,main函数里用switch来选择操作函数。
    实现了以下功能:

    人员编号在生成人员信息时同时生成,每输入一个人员信息编号顺序加1
    程序对所有人员有提升级别的功能
    月薪的计算方法是:经理拿固定月薪,兼职技术人员按工作小时数领取月薪,兼职推销员的报酬按该推销员当月销售额提成 ,销售经理既拿固定月薪也领取销售提成
    能显示全部人员信息
    能按姓名或者编号显示、查找、增加、删除或者删除全部人员和保存各类人员的信息
    显示负责人联系方式
    信息整合保存成a.txt文件
    读取a.txt文件

    People类 数据成员
    string job; //职位string name; //姓名int number; //编号int money; //工资static int sum; //公司人数
    成员函数
    People() {} //重载构造函数People(string job1, string name1,intmoney1,int number1){ job=job1; name=name1; number=number1; money=money1;} //构造一个People类 ~People(); //析构一个People类
    Main类 数据成员
    vector<People> Peo; //用容器来存放People成员易于操作,比类数组要好
    成员函数
    Main() {} //构造函数void Promote(); //升职函数, 查找人员名字来操作升职void Show(); //显示总信息void Look(); //根据名字来查找 成员 void Increase(); //增加一个成员void Delete(); //根据名字来删除一个成员void all(); //删除全部人员void write(); //将数据存入txt文件void read(); //从txt文件中读取内容int Sum() //显示公司人数
    三 系统实现3.1 技术架构编译环境:CodeBlocks
    面向对象的相关技术:

    封装
    重载
    vector容器装People类成员,二进制文本文件的存储与读取
    改变黑框的构造
    system("mode con cols=150 lines=30");system("color 3f");
    system(“cls”) 来清空画面
    switch()来选择操作的函数
    system(“pause”); 按任意键继续
    getchar(); 吸收键盘输入

    3.2 代码实现#include <iostream>#include <iomanip>#include <fstream>#include <cstdio>#include <cstdlib>#include <vector>#include <cstring>#define four 20000;#define three 15000;#define two 8000;#define one 4000;using namespace std;class People //定义一个人员的类{public: string job; //职位 string name; //名字 int number; //编号 int money; //工资 People() {} People(string job1, string name1,int money1,int number1) //构造函数初始化一个人员 { job=job1; name=name1; number=number1; money=money1; } ~People(); static int sum; //这是static类型的sum 控制编号数字};People::~People(){}class Main //总的控制台 控制人员;人员存放在vector的容器中 通过内部函数进行各种操作{protected: vector<People> Peo;public: Main() {} void Promote(); //升职函数 void Show(); //显示总信息 void Look(); //查找 void Increase(); //增加一个成员 void Delete(); //删除一个成员 void all(); void write(); //讲数据存入txt文件 void read(); int Sum() //显示公司人数 { return Peo.size(); }};void Main::read() //读取{ // char www[200]; cout<<"* 融资股份有限公司人员管理系统 *"<<endl; ifstream ifs("C:\\Users\\Administrator\\Desktop\\a.txt",ios::in|ios::binary); if(ifs.peek()==EOF) cout<<"文件为空"<<endl; if(ifs) { People Peo1; // ifs.getline(www,200,'*'); // cout<<www<<endl; for(int i=0; i<Peo.size(); i++) { ifs.read(reinterpret_cast<char*>(&Peo1),sizeof(Peo1)); cout<<"名字:"<<Peo1.name<<setw(15)<<"编号:"<<Peo1.number<<setw(15)<<"职务:" <<Peo1.job<<setw(15)<<"工资:"<<Peo1.money<<endl; } }}void Main::all() //删除全部{ vector <People>::iterator w; if(Peo.size()==0) cout<<"删除失败\n"; else { cout<<"总人数是"<<Peo.size()<<endl; for(w=Peo.begin(); w!=Peo.end(); w++) { w=Peo.erase(w); w--; } cout<<"删除成功"<<endl; }}void Main::write() //存入txt文件模块{ // string str="月底工资账单总览*"; ofstream ofs; ofs.open("C:\\Users\\Administrator\\Desktop\\a.txt",ios::out|ios::binary); //打开txt文件,操作是每次打开删除之前的数据 if(ofs) { if(Peo.size()==0) cout<<"操作失败"<<endl; else { // ofs.write(str.c_str(), strlen(str.c_str())); // ofs.flush(); for(int i=0; i<Peo.size(); i++) { ofs.write(reinterpret_cast<char*>(&Peo[i]),sizeof(Peo[i])); ofs.flush(); } cout<<"copy成功"<<endl; ofs.close(); //每次打开文件后最后都要关掉,不然存不进去 } } else cout<<"文件打开失败";}void Main::Promote() //升职模块{ string name1; //升职人员的名字 偷懒了 name后面加了个1就算新的名字 string zhiwu; //所升的职务 int money1,numb,j; //升职后的工资 升职人数 和for循环所用的一个变量j cout<<"请输入要升职的人数:"<<endl; cin>>numb; if(numb==0||Peo.size()==0||numb>Peo.size()) { cout<<"操作失败\n"; return; } for(int i=1; i<=numb; i++) { cout<<"请输入第"<<i<<"升职人的名字"<<endl; cin>>name1; for( j=0; j<Peo.size(); j++) if(Peo[j].name==name1) { cout<<"请输入要升职的职位"<<endl; cin>>zhiwu; cout<<"请输入职位的工资"<<endl; cin>>money1; Peo[j].job=zhiwu; Peo[j].money=money1; } if(j==Peo.size()) //没有此人的判断条件有三个 1.输入的人数为0 2.输入的人数超过总人数 3.输入的名字没有存在 cout<<"没有此人"<<endl; }}void Main::Show() // 显示数据模块{ cout<<" 月底工资账单总览 "<<endl; for(int i=0; i<Peo.size(); i++) { cout<<"名字:"<<Peo[i].name<<setw(15)<<"编号:"<<Peo[i].number<<setw(15)<<"职务:" <<Peo[i].job<<setw(15)<<"工资:"<<Peo[i].money<<endl; } if(Peo.size()==0) cout<<"无人员"<<endl;}void Main::Look() //查找模块{ string name1; cout<<"请输入要查找人的姓名:"<<endl; cin>>name1; for(int i=0; i<Peo.size(); i++) if(Peo[i].name==name1) { cout<<"名字:"<<Peo[i].name<<setw(15)<<"编号:"<<Peo[i].number<<setw(15)<<"职务:" <<Peo[i].job<<setw(15)<<"工资:"<<Peo[i].money; } cout<<"没有此人"<<endl;}void Main::Increase() //增加人员模块{ string name1; string job1; int money1; int numb; cout<<"请输入要增加的人数:"<<endl; cin>>numb; for(int i=1; i<=numb; i++) { cout<<"请输入第"<<i<<"个人的名字:"<<endl; cin>>name1; cout<<"请输入职务:"<<endl; cin>>job1; cout<<"请输入工资:"<<endl; cin>>money1; People::sum++; People ch(job1,name1,money1,People::sum); Peo.push_back(ch); }}void Main::Delete() //删除模块{ string name1; int numb,j,sum; cout<<"请输入要删除的人数:"<<endl; cin>>numb; if(numb==0||Peo.size()==0||numb>Peo.size()) { cout<<"操作失败\n"; return; } for(int i=1; i<=numb; i++) { cout<<"请输入要删除的第"<<i<<"个人的姓名:"<<endl; cin>>name1; for(j=0; j<Peo.size(); j++) { sum=Peo.size(); if(Peo[j].name==name1) { Peo.erase(Peo.begin()+j); cout<<"删除成功"<<endl; break; } } if(j==sum) cout<<"没有此人"<<endl; }}int People::sum=0;int main(){ system("mode con cols=150 lines=30"); system("color 3f"); Main M; while(1) { system("cls"); cout<<"*----------------------------------------------------------------*\n"; cout<<"*注:经理固定月薪:20000¥"<<setw(15)<<"销售经理:15000¥"<<setw(15)<<"兼职技术人员:8000¥"<<setw(15)<<"兼职推销员:4000¥ *"<<endl; cout<<"* 融资股份有限公司人员管理系统 *"<<endl; cout<<"* 月底工资账单总览 *"<<endl; cout<<"* 1/增加人员及其个人信息 *"<<endl; cout<<"* 2/删除人员及其个人信息 *"<<endl; cout<<"* 3/删除全部人员及其个人信息 *"<<endl; cout<<"* 4/查找人员及其个人信息 *"<<endl; cout<<"* 5/升职操作 *"<<endl; cout<<"* 6/公司人员总数 *\n"; cout<<"* 7/月底工资账单总览 *"<<endl; cout<<"* 8/负责人联系方式 *"<<endl; cout<<"* 9/信息整合保存成a.txt文件 *"<<endl; cout<<"* A/读取a.txt文件 *"<<endl; cout<<"-------------------------------------------------------------------*"<<endl; char num=getchar(); //getchar()吸收键盘输入的字符数字 便可实现人机交互,可选择操作 switch(num) { case '1': M.Increase(); break; case '2': M.Delete(); break; case '3': M.all(); break; case '4': M.Look(); break; case '7': M.Show(); break; case '8': cout<<"微信:1596321;手机号:1598321"<<endl; break; case '9': M.write(); break; case '5': M.Promote(); break; case '6': cout<<"公司总人数:"<<M.Sum()<<endl; break; case 'A': M.read(); default: break; } system("pause"); //按任意键继续,实现了画面冻结 getchar(); //这个getchar()是为了吸收删一个输入的回车键,防止下次循环中num吸收了这个回车键 }}
    四 运行效果4.1 系统界面显示系统运行后,显示运行界面,如图:

    在系统功能界面选择操作序列,如果输入的数字不在序列里,则会报错,如图所示:

    4.2 人员信息增加在系统功能界面选择1,显示增加操作,如图所示:
    首先输入要操作的人数然后依次输入第几个人的名字,职务和工资。
    然后按任意键继续。

    4.3 人员信息删除选择2进入删除操作界面,如果输入的数字大于实际数据则会报错,按任意键继续,如图所示:

    输入正确数字后进行实际的删除操作,按任意键继续;如图所示:

    选择3是执行删除全部人员信息的操作,如果人数为0时,操作失败,按任意键继续;如果人数大于0,人员全部删,按任意键继续;如图所示:

    4.4 人员升职选择5,进入升职界面。如果输入人数大于实际人数则会报错,如果输入人员不在内存也会报错,按任意键继续;
    如图所示:


    4.5 人员信息查询选择4,进入查询界面,按任意键继续;如图所示:

    4.6 人员数量统计选择6,显示实际人数,按任意键继续;如图所示:

    4.7 月底工资账单总览选择7,显示月底工资账单总览,如图所示:

    4.8 人员信息文件管理选择9,将内存中的数据存入 a.txt 外部文档,如图所示:

    选择 A,读取 a.txt 文件中的内容,如图所示:

    五 总结
    每次操作完要用 cls 进行画面清除 保持了界面的整洁和易于操作
    用getchar())来吸收键盘输入的字符
    用switch()函数来进行选择操作的函数
    system(“pause”);按任意键继续,实现了画面冻结
    最后也要加getchar(); 这个getchar()是为了吸收删一个输入的回车键,防止下次循环中num吸收了这个回车键
    存放类成员用容器比较方便,易于操作
    可以用 system(“mode concols=150 lines=30”); system(“color 3f”);这样的函数来美化界面

    六 参考文献C++程序设计教程[M]. 北京:人民邮电出版社,2015.
    2 评论 30 下载 2018-10-16 20:44:57 下载需要7点积分
  • 基于Qt实现的旅行模拟器

    一、设计任务的描述城市之间有三种交通工具(汽车、火车和飞机)相连,某旅客于某一时刻向系统提出旅行要求,系统根据该旅客的要求为其设计一条旅行线路并输出;系统能查询当前时刻旅客所处的地点和状态(停留城市/所在交通工具)。
    二、功能需求说明及分析
    城市总数不少于10个(13个)
    建立汽车、火车和飞机的时刻表(航班表)

    有沿途到站及票价信息不能太简单(不能总只是1班车次相连)
    旅客的要求包括:起点、终点、途经某些城市和旅行策略
    旅行策略有:

    最少费用策略:无时间限制,费用最少即可最少时间策略:无费用限制,时间最少即可限时最少费用策略:在规定的时间内所需费用最省

    三、总体方案设计说明软件开发环境、总体结构和模块划分等。
    Windows下开发,使用Qt Creator作为IDE,MySQL数据库进行时刻表调用,C++语言编程。
    目前为单一窗口,如果需要显示地图,可以增加一个窗口。
    模块:

    Main:调用其他各个模块
    Widget:主窗口,其上有输入信息和输出信息
    Route:地图窗口,可以显示地图以及当前的位置,预计路径行程等
    Passenger:储存输入的信息,当前状态,计算后的预计行程等
    LogFile:记录日志
    TimeTable:进行数据库的访问,将访问数据库封装成该类的方法

    应该还有一个计时器来模拟时间流动。


    四、数据结构说明和数据字典struct Status//保存当前状态{ char transport;//当前交通工具 QString curCity;//当前城市 QString nextMove;//下一步};class Passenger//当前乘客的信息{public: Passenger(); enum POLICY{minTime,minCost,timeLimitCost}; void setStart(QString s); QString getStart(); void setEnd(QString e); QString getEnd(); void setPolicy(int p); int getPolicy(); void setLimitTime(double L); double getLimitTime(); void setWayCities(QList<QPair<QString, double>> W); QList<QPair<QString, double>> getWayCities(); void setSequence(bool checked); bool isSequence();private: QString start;//起点 QString end;//终点 int policy;//策略 double limitTime;//限时最低价格的限制时间 QList<QPair<QString, double>> wayCities;//途经城市 bool sequence;//是否有顺序 Status curStatus;//暂时打算用作储存当前状态};struct EdgeType { int cost = A_BIG_INT;};//邻接矩阵class Graph //{public: Graph(); void CreateGraph_MinCost();//根据数据库建立最低价格的矩阵 int Dijkstra(QString start_city, QString end_city, vector<QString> &out); //迪杰斯特拉求最短路径 int LeastCost(QString start_city, QString end_city, vector<QString> &mid_city, int isOrdered, vector<QString> &rout); //求总路径private: vector<QString> vertex;//城市名表 EdgeType arc_MinCost[100][100];//邻接矩阵,可看作边表 int numVertex;//图中当前的顶点数 int numEdge; //图中当前的边数};struct Info//数据库中一条数据{ QString trainnumber; QString departcity; QString arrivecity; QTime departtime; QTime arrivetime; int price; int id;};class TimeTable{public: TimeTable(); ~TimeTable(); static int getMinPrice(QString start, QString goal); //根据起点终点从数据库中寻找最少价格 static Info getInfo_MinCost(QString start, QString goal); //根据起点终点从数据库中提取最少价格的那一条数据private: QSqlDatabase db; static QMap<QString,QString> full2Short; //全名映射到简称};
    五、各模块设计说明在widget窗口中输入,即时设定好passenger的成员变量,点击开始后,执行算法。
    最少费用:价格作为有向网的权值,利用迪杰斯特拉算法设计出路线。
    1 评论 2 下载 2019-06-30 10:37:22 下载需要9点积分
  • 基于java的仿QQ聊天工具

    一 需求分析本系统是基于java开发的聊天室。有用户注册、用户登陆、修改密码、忘记密码、添加好友、用户聊天、群聊功能。如果服务器还没有启动,则客户端是不可以登陆、注册、忘记密码,如果在运行过程中,服务器断开则系统会有提示,聊天对象如果下线发送消息后会有弹窗提示,添加好友后有是否添加好友成功提示。
    二 概要设计在客户端:当用户登录后,生成唯一的socket, 存放在Client实体类中,在整个客户端就一个Client类和一个socket。有一个窗口控制器——ChatUIList,用来记录用户和好友聊天框是否打开,当收到消息后,首先在ChatUIList中查询是否有好友的窗口,如果没有则新建聊天框弹出并显示消息,如果存在与好友的窗口则将消息追加到原聊天框并重新着重显示该窗口。在客户端还拥有一个“命令控制中心”——ChatTread类,在ChatTread类中判断并处理来自服务器中的命令(消息),如果是“message”那么客户端收到是来自好友的消息,如果是“requeste_add_friend”则是好友申请命令,类似有“WorldChat”,“accept_add_friend”,“refuse_to_add”,“changepwd”等命令。
    在服务端:有多个socket,用SockList管理连接成功的用户名及其socket。同样在服务端也有一个“命令控制中心”——ServerTread类,它负责处理来自客户端的命令(消息),判断命令的类型,并正确处理他们,给出处理结果和判断是否处理成功,将处理后的命令转发给正确的用户。
    功能设计如下:

    注册功能
    设计一个注册UI(RegisterUI)类,在打开程序后,模仿QQ在左下方有一个注册按钮,点击注册按钮后弹出注册页面,用户填完必填信息后由客户端将命令发送给服务端(如果服务器在线),服务器收到“register”命令后,连接数据库判断,如果注册成功则返回注册成功消息并弹窗提示,如果失败则弹窗提示注册失败。
    登录功能
    打开客户端后,类似QQ有登录按钮,当用户填完用户账号和用户密码并提交后,客户端将登录请求发送给给服务端判断(如果服务端在线),如果密码正确则用户登录成功,显示朋友列表(FriendsUI),否则提示密码错误或账号不存在。
    忘记密码
    在客户端右下侧有忘记密码按钮,用户点击按钮后弹出忘记密码页面(ForgetUI),用户填写用户账号后客户端将消息发往服务器,服务器在数据库中检测该账号是否存在,如果存在则显示提示问题,如果不存在则提示账号不存在。用户填写完后续相关信息后,点击“重置密码”按钮后,如果找回密码答案正确则向服务器发送修改密码请求,如果失败则弹窗提示密码错误。最后服务器将处理结果(修改密码是否成功)返还给客户端。
    单独聊天(私聊)
    用户登录成功后,双击好友后,首先判断用户与该好友是否有聊天框存在,如果不存在则创建新的聊天框(ChatUI)并在ChatUIList中登记,如果存在则将改聊天框突出显示。用户可以再聊天页面(ChatUI)发送消息,如果好友不在线,服务器会返回好友不在线提示,客户端弹窗提示,如果好友在线收到消息则无提示(类似Linux,没有消息就是好消息)。好友收到消息时,在ChatUIList中查询是否有与该好友的聊天窗口,如果没有则新建窗口显示并在ChatUIList中注册,如果存在则直接将消息追加到聊天窗口上并突出显示。
    多人聊天(群聊)
    这里实现的多人聊天式世界喊话,即在线用户都能收到世界喊话的消息,没有好友限制,实现与单独聊天类似。不同的是,服务器收到“世界喊话”命令后,在SocketList中查询当前在线用户,并将世界喊话消息发送给这些用户。
    添加好友
    在好友列表页面左下角有添加好友按钮,点击该按钮后弹出添加好友框(AddFriendUI),在添加好友框中重复输入两次欲添加的好友name便可向服务器发送好友请求。当用户收到好友请求后,同意或拒绝都像添加方反馈,添加成功后重新登录便可刷新好友列表。
    修改密码
    在好友列表右下方有修改密码按钮,点击该按钮后弹出修改密码框(ChangePwdUI),在这里只需要重复输入两次新密码即可修改密码,是否修改成功服务器都会做出应答,客户端有弹窗提示。在服务端对数据库进行操作,由于可能数据库会出错,如果数据库未成功修改密码,那么要提醒客户。

    三 详细设计与实现3.1 开发环境此项目运行在Windows 10上,使用Eclipse作为IDE,用MySQL作为数据库。以Java为主要设计语言。
    3.2 系统总体结构设计如下图所示:

    3.3 系统流程逻辑设计如下图所示:

    3.4 数据库表设计数据库名为myqquser, 此数据库中若干个表,一个用户表(tb_user),用来存储用户的信息,如用户名,用户密码,用户问题,用户答案;每一个用户有一个好友表,好友表里存着用户的好友名。
    3.4.1 数据库构成此时数据库中有三个用户分别为inforSec、 zzz、sdust,tb_user中存放着用户的信息,inforSec_friends中存放着inforSec的好友信息,zzz_friends存放着zzz的好友信息,sdust_friends中存放着sdust的好友列表。

    3.4.2 用户表(tb_user)的结构及存放的数据
    3.4.3 某一用户好友表结果及数据
    3.5 客户端关键类与方法UI类
    MainFrame、FriendsUI、ChatUI、ChangeUI、AddFriendUI、RegeditUI、ForgetUI分别为主窗口页面、好友列表页面、聊天窗口、修改密码窗口、添加好友窗口、注册窗口、忘记密码窗口。这一部分主要是显示处理和逻辑处理。
    消息处理、通信类

    ChatUIList类主要记录客户端打开的聊天页面,处理与好友的消息弹窗Client类中有socket负责和服务器通信ChatTreat类是客户端的消息处理中心,处理来自服务器的各种消息并做出相应
    3.6 服务端关键类与方法UI类
    服务器端只有一个UI页面——StartServerFrame,因为服务器没有太多消息要显示,所以一个启动窗口即可。
    消息处理、通信类
    Service类中有socket,负责与客户端建立通信,每个建立的通信都存储在SocketList中,供服务器查询哪些用户上线。ServerThread类是服务器端的控制中心,负责处理来自用户端的消息,并转发给正确的用户,有时还会对数据库进行操作。
    数据库处理相关
    DBHelper类负责和数据库建立连接,UserService类负责处理具体的和数据库交互的内容,如查询用户账号和密码是否匹配、修改密码、注册用户、忘记密码、添加好友等操作。
    3.7 客户端关键代码3.7.1 主页面(MainFrame)public class MainFrame extends JFrame implements ActionListener, FocusListener { private static final long serialVersionUID = 1L; private static final String _txt_account = "QQ密码/手机/邮箱"; private static final String _txt_pwd = "密码"; private static final String _txt_title = "QQ登录"; private static final String _txt_registe = "注册"; private static final String _txt_forget = "忘记密码"; private static final String _txt_blank = ""; private JTextField account; private JPasswordField pwd; private JLabel upper_frame; private JPanel lower_frame, center_frame; private JButton login, register, forget; MainFrame() { //部分的形成 init(); //整体形成 add(upper_frame, BorderLayout.NORTH); add(center_frame, BorderLayout.CENTER); add(lower_frame, BorderLayout.SOUTH); ImageIcon logo = new ImageIcon("image/logo.jpg"); //左上方小图标 setIconImage(logo.getImage()); setBounds(500, 230, 430, 360); //设定大小及位置 setResizable(false); //登录框大小固定,不允许通过拖、拉改变大小 setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); //设置窗口右上角的叉号,点击叉号窗口关闭 注意不能EXIT_ON_CLOSE做参数的,用它时候使用的是System.exit方法退出应用程序。故会关闭所有窗口。 setTitle(_txt_title); setVisible(true); //说明数据模型已经构造好了,允许JVM可以根据数据模型执行paint方法开始画图并显示到屏幕上,一般放在最后一句 } public void init() { //账号输入块 account = new JTextField(_txt_account); account.setName("account"); account.setForeground(Color.gray); account.addFocusListener(this); //产生事件响应用户行为 //密码输入块 pwd = new JPasswordField(_txt_pwd); pwd.setName("pwd"); pwd.setForeground(Color.gray); pwd.setEchoChar('\0'); //启动后密码框内一定为 “密码” pwd.addFocusListener(this); //注册模块 register = new JButton(_txt_registe); register.setBorderPainted(false); register.setBorder(BorderFactory.createRaisedBevelBorder()); //斜面边框(凸) register.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); register.addActionListener(this); //忘记密码模块 forget = new JButton(_txt_forget); forget.setBorderPainted(false); forget.setBorder(BorderFactory.createRaisedBevelBorder()); forget.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); forget.addActionListener(this); //login按钮模块 login = new JButton(); ImageIcon login_image = new ImageIcon("image/login_image.png"); login_image.setImage(login_image.getImage().getScaledInstance(430, 35, Image.SCALE_DEFAULT)); login.setIcon(login_image); login.setBackground(Color.white); login.setBorderPainted(false); //如果进度条应该绘制边框,则为 true;否则为 false login.setBorder(null); //设置此组件的边框 无 login.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); //将光标设为 “小手”形状 login.addActionListener(this); //qq登录框架上半部分(无按钮之类的内容,只有一张图片) ImageIcon upper_image = new ImageIcon("image/upper_image.png"); upper_image.setImage(upper_image.getImage().getScaledInstance(430, 160, Image.SCALE_DEFAULT)); upper_frame = new JLabel(upper_image); //qq登录中间部分 (账号、密码、 注册、forget) center_frame = new JPanel(); center_frame.setName("center_frame"); center_frame.setLayout(null); center_frame.setLayout(new GridLayout(3, 3, 3, 15)); //这里用到3行3列的空间, 用空格填充 center_frame.add(new JLabel(_txt_blank, JLabel.CENTER)); center_frame.add(account); center_frame.add(new JLabel(_txt_blank, JLabel.CENTER)); center_frame.add(new JLabel(_txt_blank, JLabel.CENTER)); center_frame.add(pwd); center_frame.add(new JLabel(_txt_blank, JLabel.CENTER)); center_frame.add(register); center_frame.add(new JLabel(_txt_blank, JLabel.CENTER)); center_frame.add(forget); center_frame.setBackground(Color.white); //qq登录框架的下半部分login lower_frame = new JPanel(); lower_frame.setName("lower_frame"); lower_frame.setLayout(null); //lower_frame.setLayout(new GridLayout(1, 3, 3, 15)); lower_frame.setLayout(new GridLayout(0, 1)); lower_frame.add(login); } //按钮的点击事件用actionPerformed @Override public void actionPerformed(ActionEvent e){ /* * 1:如果点击了登录按钮 首先判断帐号或者密码是否为空 然后封装为CommandTranser对象 向服务器发送数据 服务器通过与数据库的比对 * 来验证帐号密码, * 2:如果点击了注册账号就弹出注册页面, 信息填写完整后连接服务器 * 3:如果点击了忘记密码弹出找回密码页面 */ //处理登录(login)页面 if(e.getSource() == login){ String user_name = account.getText().trim(); String user_pwd = new String(pwd.getPassword()).trim(); if("".equals(user_name) || user_name == null || _txt_account.equals(user_name)) { JOptionPane.showMessageDialog(null, "请输入帐号!!"); return; } if("".equals(user_pwd) || user_pwd == null || _txt_pwd.equals(user_pwd)) { JOptionPane.showMessageDialog(null, "请输入密码!!"); return; } User user = new User(user_name, user_pwd); CommandTranser cmd = new CommandTranser(); cmd.setCmd("login"); cmd.setData(user); cmd.setReceiver(user_name); cmd.setSender(user_name); //实例化客户端 连接服务器 发送数据 密码是否正确? Client client = new Client(); //创建唯一的客户端(用于接受服务器发来的消息, socket接口), client.sendData(cmd); //发送数据 cmd = client.getData(); //接受反馈的消息 if(cmd != null) { if(cmd.isFlag()) { this.dispose(); //关闭MainFrame页面 JOptionPane.showMessageDialog(null, "登陆成功"); user = (User)cmd.getData(); FriendsUI friendsUI = new FriendsUI(user, client); //将user的全部信息传到FriendsUI中,并将唯一与服务器交流的接口传到FriendUI中 这里传client仅为了发送消息 ChatTread thread = new ChatTread(client, user, friendsUI); //这里传client为了收消息, 整个客户端用一个 ChatTread,一个client thread.start(); }else { JOptionPane.showMessageDialog(this, cmd.getResult()); } } } //处理注册(register)页面 if(e.getSource() == register){ RegisterUI registerUI = new RegisterUI(this); // } //处理找回密码(forget)页面 if(e.getSource() == forget){ ForgetUI forgetUI = new ForgetUI(this); } } //鼠标的点击或移动之类的用focuslistener @Override public void focusGained(FocusEvent e) { //处理账号输入框 if(e.getSource() == account){ if(_txt_account.equals(account.getText())){ account.setText(""); account.setForeground(Color.BLACK); } } //处理密码输入框 if(e.getSource() == pwd){ if(_txt_pwd.equals(pwd.getText())){ pwd.setText(""); pwd.setEchoChar('*'); pwd.setForeground(Color.BLACK); } } } @Override public void focusLost(FocusEvent e) { //处理账号输入框 if(e.getSource() == account){ if("".equals(account.getText())){ account.setForeground(Color.gray); account.setText(_txt_account); } } //处理密码输入框 if(e.getSource() == pwd){ if("".equals(pwd.getText())){ pwd.setForeground(Color.gray); pwd.setText(_txt_pwd); pwd.setEchoChar('\0'); } } } public static void main(String[] args) { // TODO Auto-generated method stub MainFrame mainframe = new MainFrame(); }}
    3.7.2 消息处理核心——ChatTread类public class ChatTread extends Thread{ private Client client; private boolean isOnline = true; private User user; //如果同意好友请求, 则刷新好友列表 private FriendsUI friendsUI; //刷新好友列表用 private String username; //如果创建新的聊天窗口(chatUI)那么必须将username传进去 用来发送消息 public ChatTread(Client client, User user, FriendsUI friendsUI) { this.client = client; this.user = user; this.friendsUI = friendsUI; this.username = user.getUsername(); //this.chat_windows = chat_windows; } public boolean isOnline() { return isOnline; } public void setOnline(boolean isOnline) { this.isOnline = true; } //run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间, //便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务 @Override public void run() { if(!isOnline) { JOptionPane.showMessageDialog(null, "unbelievable !!!"); return; } while(isOnline) { CommandTranser cmd = client.getData(); //与服务器端相同处理接收到的消息(命令) //这里处理来自服务器的消息(命令) if(cmd != null) { execute(cmd); //System.out.println(cmd.getCmd()); } } } //处理消息(命令) private void execute(CommandTranser cmd) { //登录、忘记密码、注册消息未在此处处理 System.out.println(cmd.getCmd()); //聊天消息请求 if("message".equals(cmd.getCmd())) { if(cmd.isFlag() == false) { JOptionPane.showMessageDialog(null, cmd.getResult()); return; } //查询是否有与该好友的窗口该窗口 String friendname = cmd.getSender(); ChatUI chatUI = ChatUIList.getChatUI(friendname); if(chatUI == null) { chatUI = new ChatUI(username, friendname, username, client); ChatUIEntity chatUIEntity = new ChatUIEntity(); chatUIEntity.setName(friendname); chatUIEntity.setChatUI(chatUI); ChatUIList.addChatUI(chatUIEntity); } else { chatUI.show(); //如果以前创建过仅被别的窗口掩盖了 就重新显示 } Date date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat( "yy-MM-dd hh:mm:ss a"); String message = friendname + "说:" + (String) cmd.getData() + "\t" + sdf.format(date) + "\n"; chatUI.getChatWin().append(message); //追加消息 return; } if("WorldChat".equals(cmd.getCmd())) { //查询是否有与该好友的窗口该窗口 String friendname = cmd.getSender(); ChatUI chatUI = ChatUIList.getChatUI("WorldChat"); if(chatUI == null) { chatUI = new ChatUI("WorldChat", "WorldChat", user.getUsername(), client); ChatUIEntity chatUIEntity = new ChatUIEntity(); chatUIEntity.setName("WorldChat"); chatUIEntity.setChatUI(chatUI); ChatUIList.addChatUI(chatUIEntity); } else { chatUI.show(); //如果以前创建过仅被别的窗口掩盖了 就重新显示 } Date date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat( "yy-MM-dd hh:mm:ss a"); String message = friendname + "说:" + (String) cmd.getData() + "\t" + sdf.format(date) + "\n"; chatUI.getChatWin().append(message); //追加消息 return; } if("requeste_add_friend".equals(cmd.getCmd())) { if(cmd.isFlag() == false) { JOptionPane.showMessageDialog(null, cmd.getResult()); return; } String sendername = cmd.getSender(); int flag = JOptionPane.showConfirmDialog(null, "是否同意" + sendername + "的好友请求", "好友请求", JOptionPane.YES_NO_OPTION); System.out.println(flag); if(flag == 0) { cmd.setCmd("accept_add_friend"); } else { cmd.setCmd("refuse_add_friend"); } cmd.setSender(username); cmd.setReceiver(sendername); client.sendData(cmd); return; } if("accept_add_friend".equals(cmd.getCmd())) { JOptionPane.showMessageDialog(null, cmd.getResult()); return; } if("refuse_to_add".equals(cmd.getCmd())) { JOptionPane.showMessageDialog(null, cmd.getResult()); return; } if("changepwd".equals(cmd.getCmd())) { JOptionPane.showMessageDialog(null, cmd.getResult()); return; } return; }}
    3.7.3 负责通信——Client 类public class Client { private int port = 2222; private String Sever_address = "127.0.0.1"; //服务器主机ip private Socket socket; //实例化, 建立连接 public Client(){ try { socket = new Socket(Sever_address, port); } catch(UnknownHostException e) { JOptionPane.showMessageDialog(null, "服务器端未开启"); }catch(IOException e) { JOptionPane.showMessageDialog(null, "服务器端未开启"); } } public Socket getSocket() { return socket; } public void setSocket(Socket socket) { this.socket = socket; } //向服务端发送数据 public void sendData(CommandTranser cmd) { ObjectOutputStream oos = null; //主要的作用是用于写入对象信息与读取对象信息。 对象信息一旦写到文件上那么对象的信息就可以做到持久化了 try { if(socket == null) { return; } oos = new ObjectOutputStream(socket.getOutputStream()); oos.writeObject(cmd); } catch(UnknownHostException e) { JOptionPane.showMessageDialog(null, "服务器端未开启"); }catch(IOException e) { JOptionPane.showMessageDialog(null, "服务器端未开启"); } } //接受服务端发送的消息 public CommandTranser getData() { ObjectInputStream ois = null; CommandTranser cmd = null; if(socket == null) { //System.out.println("weishenme"); return null; } try { ois = new ObjectInputStream(socket.getInputStream()); cmd = (CommandTranser) ois.readObject(); } catch (IOException e) { return null; } catch (ClassNotFoundException e) { return null; } return cmd; }}
    3.8 服务端关键代码3.8.1 CommandTranser类——用于命令传递public class CommandTranser implements Serializable { private static final long serialVersionUID = 1L; private String sender = null;// 发送者 private String receiver = null;// 接受者 private Object data = null; // 传递的数据 private boolean flag = false; // 指令的处理结果 private String cmd = null; // 服务端要做的指令 private String result = null; //处理结果 public String getSender() { return sender; } public String setSender(String sender) { return this.sender = sender; } public String getReceiver() { return receiver; } public String setReceiver(String receiver) { return this.receiver = receiver; } public Object getData() { return data; } public Object setData(Object data) { return this.data = data; } public boolean isFlag() { return flag; } public boolean setFlag(boolean flag) { return this.flag = flag; } public String getResult() { return result; } public String setResult(String result) { return this.result = result; } public String getCmd() { return cmd; } public String setCmd(String cmd) { return this.cmd = cmd; }}
    3.8.2 ServerThread类——服务器端的消息控制中心public class ServerThread extends Thread{ private Socket socket; public ServerThread(Socket socket) { this.socket = socket; } @Override public void run() { ObjectInputStream ois = null; ObjectOutputStream oos1 = null; ObjectOutputStream oos2 = null; //ObjectOutputStream oos3 = null; while(socket != null) { try { ois = new ObjectInputStream(socket.getInputStream()); CommandTranser cmd = (CommandTranser) ois.readObject(); //执行命令来自客户端的请求 cmd = execute(cmd); //消息对话请求,服务器将sender发来的消息发送给receiver if("message".equals(cmd.getCmd())) { //如果 msg.ifFlag即 服务器处理成功 可以向朋友发送信息 如果服务器处理信息失败 信息发送给发送者本人 if(cmd.isFlag()) { //System.out.println("对方在线"); oos1 = new ObjectOutputStream(SocketList.getSocket(cmd.getReceiver()).getOutputStream()); } else { //System.out.println("对方未在线"); oos2 = new ObjectOutputStream(socket.getOutputStream()); } } if ("WorldChat".equals(cmd.getCmd())) { HashMap<String, Socket> map = SocketList.getMap(); Iterator<Map.Entry<String, Socket>> it = map.entrySet().iterator(); while(it.hasNext()) { Map.Entry<String, Socket> entry = it.next(); if(!entry.getKey().equals(cmd.getSender())) { oos1 = new ObjectOutputStream(entry.getValue().getOutputStream()); oos1.writeObject(cmd); } } continue; } //登录请求 将数据发送给sender if ("login".equals(cmd.getCmd())) { oos1 = new ObjectOutputStream(socket.getOutputStream()); } //注册请求 将数据发送给sender if ("register".equals(cmd.getCmd())) { System.out.println("向客户端发送消息"); oos1 = new ObjectOutputStream(socket.getOutputStream()); } //添加好友请求将数据发送给 receiver if ("requeste_add_friend".equals(cmd.getCmd())) { //在线,将请求发给receiver if(cmd.isFlag()) { oos1 = new ObjectOutputStream(SocketList.getSocket(cmd.getReceiver()).getOutputStream()); } else { //不管在不在线都要向发送方提示消息发送成功 oos2 = new ObjectOutputStream(socket.getOutputStream()); } } //同意添加好友请求将数据发送给 receiver和sender if ("accept_add_friend".equals(cmd.getCmd())) { //无论是否成功插入数据库都要将结果反馈,但有可能最初请求的客户下线了 oos1 = new ObjectOutputStream(socket.getOutputStream()); if(SocketList.getSocket(cmd.getReceiver()) != null) { oos2 = new ObjectOutputStream(SocketList.getSocket(cmd.getReceiver()).getOutputStream()); } } //拒绝添加好友请求将数据发送给 receiver if ("refuse_to_add".equals(cmd.getCmd())) { //被拒绝方在线 if(cmd.isFlag()) { oos1 = new ObjectOutputStream(SocketList.getSocket(cmd.getReceiver()).getOutputStream()); }else { //被拒方不在线则向拒绝方发送消息 oos2 = new ObjectOutputStream(socket.getOutputStream()); } } //修改资料请求 发送给sender 功能暂未实现 if ("changeinfo".equals(cmd.getCmd())) { oos1 = new ObjectOutputStream(socket.getOutputStream()); } //修改密码请求 将数据发送给sender if ("changepwd".equals(cmd.getCmd())) { oos1 = new ObjectOutputStream(socket.getOutputStream()); } //忘记密码 发送给sender if ("forgetpwd".equals(cmd.getCmd())) { oos1 = new ObjectOutputStream(socket.getOutputStream()); } //用户下线 if("logout".equals(cmd.getCmd())) { // } if(oos1 != null) { oos1.writeObject(cmd); } if(oos2 != null) { oos2.writeObject(cmd); } } catch(IOException e) { socket = null; } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } //处理客户端发来的命令 private CommandTranser execute(CommandTranser cmd) { //登录请求 if("login".equals(cmd.getCmd())) { UserService userservice = new UserService(); User user = (User)cmd.getData(); cmd.setFlag(userservice.checkUser(user)); //如果登陆成功,将该客户端加入已经连接成功的map集合里面 并且开启此用户的接受线程 if(cmd.isFlag()) { // 将该线程加入连接成功的map集合 SocketEntity socketEntity = new SocketEntity(); socketEntity.setName(cmd.getSender()); socketEntity.setSocket(socket); SocketList.addSocket(socketEntity); //从数据库获取其好友列表并将其好友列表发送至客户端 cmd.setData(userservice.getFriendsList(user)); cmd.setResult("登陆成功"); } else { cmd.setResult("密码错误"); } } //注册请求 if("register".equals(cmd.getCmd())) { UserService userservice = new UserService(); User user = (User)cmd.getData(); cmd.setFlag(userservice.registerUser(user)); //如果注册成功 if(cmd.isFlag()) { SocketEntity socketEntity = new SocketEntity(); socketEntity.setName(cmd.getSender()); socketEntity.setSocket(socket); SocketList.addSocket(socketEntity); cmd.setData(userservice.getFriendsList(user)); //刚注册的肯定没有好友 cmd.setResult("注册成功"); } else { cmd.setResult("注册失败可能该用户已存在"); } } //修改资料请求 功能暂未实现 if("changeInfo".equals(cmd.getCmd())) { UserService userservice = new UserService(); User user = (User)cmd.getData(); cmd.setFlag(userservice.changeInfo(user)); if(cmd.isFlag()) { cmd.setResult("修改信息成功"); } else { cmd.setResult("修改信息失败"); } } //添加好友 if("requeste_add_friend".equals(cmd.getCmd())) { //检查用户是否在线 if(SocketList.getSocket(cmd.getReceiver()) != null) { cmd.setFlag(true); cmd.setResult("对方收到了您的好友请求"); } else { cmd.setFlag(false); cmd.setResult("当前用户不在线或者改用户不存在"); } } //同意添加好友请求 if("accept_add_friend".equals(cmd.getCmd())) { UserService userservice = new UserService(); cmd.setFlag(userservice.addFriend(cmd.getReceiver(), cmd.getSender())); if(cmd.isFlag()) { cmd.setResult("好友添加成功请重新登陆刷新"); } else { cmd.setResult("服务器故障导致添加好友失败或者您们已经为好友"); } } //拒绝添加好友 if("refuse_to_add".equals(cmd.getCmd())) { //检查是否在线 if(SocketList.getSocket(cmd.getReceiver()) != null) { cmd.setFlag(true); cmd.setResult("您被 " + cmd.getSender() + " 拒绝了"); } else { cmd.setFlag(false); cmd.setResult("对方不在线不知道你拒绝了他的好友请求"); } } //发送消息指令 if("message".equals(cmd.getCmd())) { //检查是否在线 if(SocketList.getSocket(cmd.getReceiver()) != null) { //System.out.println("神奇"); cmd.setFlag(true); //cmd.setResult("对方成功收到您的消息"); } else { //System.out.println("神奇啊"); cmd.setFlag(false); cmd.setResult("当前用户不在线"); } } if("WordChat".equals(cmd.getCmd())) { cmd.setFlag(true); } //忘记密码指令 这里最后要讲用户的问题和答案返回 if("forgetpwd".equals(cmd.getCmd())) { UserService userservice = new UserService(); User user = (User)cmd.getData(); user = userservice.getUser(user); //如果用户存在 if(user != null ) { cmd.setResult("查询成功"); cmd.setData(user); cmd.setFlag(true); } else { cmd.setResult("用户可能不存在"); cmd.setFlag(false); } } if ("changepwd".equals(cmd.getCmd())) { UserService userservice = new UserService(); User user = (User)cmd.getData(); cmd.setFlag(userservice.changePassword(user)); System.out.println("there 111 "); System.out.println(user.getUsername()); if(cmd.isFlag()) { cmd.setResult("修改密码成功"); }else { cmd.setResult("修改密码失败"); } } if("logout".equals(cmd.getCmd())) { SocketList.getSocket(cmd.getSender()); } return cmd; }}
    3.8.3 DBHelper类——连接数据库public class DBHelper { private static final String driver = "com.mysql.cj.jdbc.Driver"; private static final String url = "jdbc:mysql://localhost:3306/myqquser?&useSSL=false&serverTimezone=UTC"; private static final String username = "root"; private static final String password = "zzzz"; private static Connection con = null; //静态代码负责加载驱动 static { try { Class.forName(driver); //Class.forName(xxx.xx.xx)的作用是要求JVM查找并加载指定的类,也就是说JVM会执行该类的静态代码段 } catch(ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() { if(con == null) { try { con = DriverManager.getConnection(url, username, password); } catch(SQLException e) { e.printStackTrace(); } } return con; }}
    3.8.4 UserService类——对数据库进行增删查改public class UserService { //login验证账号密码 public boolean checkUser(User user) { PreparedStatement stmt = null; //PreparedStatement是用来执行SQL查询语句的API之一 Connection conn = null; //与特定数据库的连接(会话)。在连接上下文中执行 SQL 语句并返回结果 ResultSet rs = null; //是数据中查询结果返回的一种对象,可以说结果集是一个存储查询结果的对象,但是结果集并不仅仅具有存储的功能,他同时还具有操纵数据的功能,可能完成对数据的更新等 conn = DBHelper.getConnection(); String sql = "select * from tb_user where user_name =? and user_pwd =?"; try { stmt = conn.prepareStatement(sql); stmt.setString(1, user.getUsername()); stmt.setString(2, user.getUserpwd()); rs = stmt.executeQuery(); if(rs.next()) { return true; } } catch(SQLException e) { e.printStackTrace(); } finally{ try { if(rs != null) { rs.close(); } if(stmt != null) { stmt.close(); } } catch(SQLException e) { e.printStackTrace(); } } return false; } //登陆后向客户端发送其好友列表 public User getFriendsList(User user) { PreparedStatement stmt = null; Connection conn = null; ResultSet rs = null; conn = DBHelper.getConnection(); String sql = "select * from " + user.getUsername() + "_friends"; ArrayList<String> friendslist = new ArrayList<String>(); //这里假设好友不超过20个 try { stmt = conn.prepareStatement(sql); //stmt.setString(1, user.getUsername() + "_friends"); 这样的话会报错 rs = stmt.executeQuery(); int count = 0; while(rs.next()) { friendslist.add(rs.getString(2)); //获取好友name count++; } user.setFriendsNum(count); user.setFriendsList(friendslist); return user; } catch(SQLException e) { e.printStackTrace(); } finally{ try { if(rs != null) { rs.close(); } if(stmt != null) { stmt.close(); } } catch(SQLException e) { e.printStackTrace(); } } return user; } //用户注册 public boolean registerUser(User user) { PreparedStatement stmt1 = null; //PreparedStatement是用来执行SQL查询语句的API之一 PreparedStatement stmt2 = null; PreparedStatement stmt3 = null; Connection conn = null; ResultSet rs = null; int insertFlag = 0; int creatFlag = 0; conn = DBHelper.getConnection(); String sql = "select * from tb_user where user_name =?"; String insertusersql = "insert into tb_user (user_name, user_pwd, user_question, user_ans) values(?, ?, ?, ?)"; String creatfriendstabsql = "CREATE TABLE " + user.getUsername() + "_friends " + "(id INT NOT NULL AUTO_INCREMENT, name VARCHAR(45) NOT NULL, PRIMARY KEY (id))"; try { stmt1 = conn.prepareStatement(sql); stmt1.setString(1, user.getUsername()); rs = stmt1.executeQuery(); if(rs.next()) { System.out.println("该用户已存在" + user.getUsername() + "***"); //用户已被注册 return false; } else { System.out.println("该用户不存在" + user.getUsername() + "***"); //向用户表插入数据 stmt2 = conn.prepareStatement(insertusersql); stmt2.setString(1, user.getUsername()); stmt2.setString(2, user.getUserpwd()); stmt2.setString(3, user.getUserQuestion()); stmt2.setString(4, user.getUserAnswer()); insertFlag = stmt2.executeUpdate(); System.out.println("向表中插入数据" + user.getUsername() + "***" + insertFlag); //创建好友表 stmt3 = conn.prepareStatement(creatfriendstabsql); creatFlag = stmt3.executeUpdate(); System.out.println("创建表" + user.getUsername() + "***" + creatFlag); if(insertFlag == 1) { return true; } System.out.println("不高兴" + user.getUsername() + "***"); //return true; } } catch(SQLException e) { e.printStackTrace(); } finally{ try { if(rs != null) { rs.close(); } if(stmt1 != null) { stmt1.close(); } if(stmt2 != null) { stmt2.close(); } if(stmt3 != null) { stmt3.close(); } } catch(SQLException e) { e.printStackTrace(); } } return false; } //添加好友 public boolean addFriend(String sender, String receiver) { PreparedStatement stmt1 = null; PreparedStatement stmt2 = null; Connection conn = null; int updateResult1 = 0; int updateResult2 = 0; conn = DBHelper.getConnection(); String sql1 = "insert into " + sender + "_friends (name) values(?)"; //String sql1 = "insert into ? (name) values(?)"; String sql2 = "insert into " + receiver + "_friends (name) values(?)"; //String sql2 = "insert into ? (name) values(?)"; try { stmt1 = conn.prepareStatement(sql1); stmt2 = conn.prepareStatement(sql2); stmt1.setString(1, receiver); stmt2.setString(1, sender); updateResult1 = stmt1.executeUpdate(); updateResult2 = stmt2.executeUpdate(); if(updateResult1 == 1 && updateResult2 == 1) { return true; } else { // 如果插入不成功的话,应该将插入成功的删除....这里不做处理了 } } catch(SQLException e) { e.printStackTrace(); } finally{ try { if(stmt1 != null) { stmt1.close(); } if(stmt2 != null) { stmt2.close(); } } catch(SQLException e) { e.printStackTrace(); } } return false; } //修改信息 public boolean changeInfo(User user) { return false; } //修改密码 忘记密码 public boolean changePassword(User user) { PreparedStatement stmt1 = null; //PreparedStatement是用来执行SQL查询语句的API之一 PreparedStatement stmt2 = null; //PreparedStatement是用来执行SQL查询语句的API之一 Connection conn = null; //与特定数据库的连接(会话)。在连接上下文中执行 SQL 语句并返回结果 ResultSet rs = null; //是数据中查询结果返回的一种对象,可以说结果集是一个存储查询结果的对象,但是结果集并不仅仅具有存储的功能,他同时还具有操纵数据的功能,可能完成对数据的更新等 int updateFlag = 0; conn = DBHelper.getConnection(); //String sql = "select * from tb_user where user_question =? and user_ans =?"; String updatesql = "update tb_user set user_pwd =? where user_name = ?"; try { stmt2 = conn.prepareStatement(updatesql); stmt2.setString(1, user.getUserpwd()); stmt2.setString(2, user.getUsername()); updateFlag = stmt2.executeUpdate(); if(updateFlag == 1) return true; //} } catch(SQLException e) { e.printStackTrace(); } finally{ try { if(rs != null) { rs.close(); } if(stmt1 != null) { stmt1.close(); } if(stmt2 != null) { stmt2.close(); } } catch(SQLException e) { e.printStackTrace(); } } return false; } //获得用户的相关信息 public User getUser(User user) { PreparedStatement stmt1 = null; PreparedStatement stmt2 = null; Connection conn = null; ResultSet rs = null; conn = DBHelper.getConnection(); String sql = "select * from tb_user where user_name =?"; try { stmt1 = conn.prepareStatement(sql); stmt1.setString(1, user.getUsername()); rs = stmt1.executeQuery(); if(rs.next()) { user.setUsername(rs.getString("user_name")); user.setUserAnswer(rs.getString("user_ans")); user.setUserQuestion(rs.getString("user_question")); return user; } } catch(SQLException e) { e.printStackTrace(); } finally{ try { if(rs != null) { rs.close(); } if(stmt1 != null) { stmt1.close(); } if(stmt2 != null) { stmt2.close(); } } catch(SQLException e) { e.printStackTrace(); } } return null; }}
    四 运行测试注册界面如下:



    登录界面如下:

    修改密码界面如下:

    私聊、群聊界面如下:
    1 评论 189 下载 2018-10-29 15:12:06 下载需要6点积分
  • 基于JSP实现的在线投票系统

    一、设计方案1.1 投票系统的功能组成投票系统功能有:选择投票和个人操作及设置,投票设置,投票结果分析,投票操作保障。
    1.1.1 选择投票和个人操作及设置
    列出所有正在进行的投票活动的简略信息,供投票人选择:(也应可查看已有结果的投票活动情况)列出信息有:投票活动的名称、发起人、投票人数、投票时间等
    点击选择某投票活动后,列出该次投票活动的详细内容介绍,可选择进入投票
    进行投票操作:要先输入验证信息(如系统自动检测该机IP地址是否合法,检测投票人是否合法,检测验证码是否合法等),如无错误才可以进行投票,按投票规则填写投票提交表,提交完成投票
    设置及记录服务器地址:即让客户端机器能顺利连接到服务器端,并且该客户端IP地址合法等
    个人资料管理:是投票人的个人资料登记、密码设置等,可参考论坛式的注册、登陆、管理模式

    1.1.2 投票设置
    设置候选人资料:

    填写个人信息(包括姓名、性别、年龄、民族、出生年月日、政治面貌、家庭住址、联系方式等……)编码:由系统自动生成,投票前后需一致推荐意见:事迹介绍、或者是个人介绍等
    设置投票时间:设置该次投票的开始及结束时间,投票人只能在投票有效期间投票
    设置投票类型、投票规则:如该次投票是单选、还是多选、(选多少人)、还是评分制(最低分、最高分多少)、是否可投弃权票、是否可投反对票、多选最终选出多少人、评分制最终选出多少人等
    设置合法投票者:设置投票机器的IP、投票人的名单等

    1.1.3 投票结果分析
    排名结果:单选的结果、多选的结果(按得票数排列,胜出人显著显示)、评分制结果(按分数排列,胜出人显著显示)
    投票的统计信息:(需要做到实时变化以及最终结果显示)包括投票的剩余时间、投票的人数情况等

    1.1.4 投票操作保障
    IP验证:验证投票人的机器IP地址是否合法
    时间验证:投票时间的控制,时间到即结束该次投票活动(对局域网,可不用考虑延时问题; 但如果是基于internet的投票,要考虑: 即客户端投票时,还在有效投票时间内,但数据传到服务器端, 已经过了有效投票时间, 这时应该如何计算? 如果要使得系统设计得更合理, 希望能实现按投票当时的时间,而不是按数据到达时间, 又要防止客户端在时间上欺骗, 应该如何设计?)
    投票人验证:投票人是本系统用户,但要验证其是否享有对某次投票活动的投票权利,并且验证其帐号、密码的正确性,不可多次投票
    投票对象验证:所投的人是否存在于候选人列表中,或是否符合本次投票活动规则(因为某些投票活动可另填自己认为可以的候选人),如不符合是否当弃权处理
    验证码验证:防止利用软件连续投票,或自动投票

    1.2 投票系统的界面组成投票系统前台界面

    投票系统后台界面

    1.3 投票系统的算法、数据结构1.3.1 投票系统前端前端主要用到了盒子模型,使用到的数据结构主要是数组,集合来用于存储从数据库查询到的投票列表以及候选人信息。
    1.3.2 投票系统后台后台使用到的算法主要是查找算法,例如从数组或集合中查找到该用户,或者查找到用户点击的投票选项。数据结构使用了Map,ArrayList来存储提示信息和用户session。
    二、开发过程2.1 投票系统的设计2.1.1 数据库设计根据实验要求,抽取出四个数据库表,分别是用户表(user),投票活动表(item),候选人信息表(item_options),投票结果表(result)。其中投票活动表和候选人表是一对多关系,投票活动表和投票结果表也是一对多关系,用户表和投票活动表是一对一关系。四个表的ER图如下所示:

    2.1.2 系统架构设计本投票系统使用MVC架构,使用JSP+HTML+JQuery+CSS作为视图层,Servlet作为控制器,JDBC+MySQL作为数据模型层。架构图如下:

    2.1.3 页面设计使用盒子模型,分为上中下三个盒子,其中数据显示主要在中间区域。设计如下:

    2.2 投票系统的实现2.2.1 实现技术利用Java语言,使用JavaWeb技术体系中的:JSP+Servlet+JDBC+MySQL技术,来开发一个web端的投票系统。
    2.2.2 数据访问层实现(请看db包和dao包下的所有文件。这里只贴DB.java)public class DB { public static final String DB_DRIVER = "com.mysql.jdbc.Driver"; public static final String DB_URL = "jdbc:mysql://127.0.0.1:3306/db_vote?characterEncoding=utf-8"; public static final String USER = "root"; public static final String PWD = "123456"; private Connection con = null; private Statement stmt = null; private PreparedStatement pstmt = null; public DB() { createConnection(); try { stmt = con.createStatement(); } catch (SQLException ex) { System.err.println("Error: " + ex.getMessage()); } } public DB(String preparedSql) { createConnection(); try { pstmt = con.prepareStatement(preparedSql,Statement.RETURN_GENERATED_KEYS); } catch (SQLException ex) { System.err.println("Error: " + ex.getMessage()); } } private void createConnection() { try { Class.forName(DB_DRIVER); con = DriverManager.getConnection(DB_URL, USER, PWD); } catch (ClassNotFoundException ex) { System.err.println("Error: 类不存在!" + ex.getMessage()); } catch (SQLException ex) { System.err.println("Error: 连接数据库失败!" + ex.getMessage()); } } public ResultSet select(String sql) { ResultSet rs = null; try { rs = stmt.executeQuery(sql); } catch (SQLException ex) { System.err.println("Error: " + ex.getMessage()); } return rs; } //preparedStatement的查询方法 public ResultSet select() { ResultSet rs = null; try { rs = pstmt.executeQuery(); } catch (SQLException ex) { System.err.println("Error: " + ex.getMessage()); } return rs; } public int update(String sql) { int result = 0; try { result = stmt.executeUpdate(sql); } catch (SQLException ex) { System.err.println("Error: " + ex.getMessage()); } return result; } //preparedStatement的更新 public int update() { int result = 0; try { result = pstmt.executeUpdate(); } catch (SQLException ex) { System.err.println("Error: " + ex.getMessage()); } return result; } public int getInsertId(){ int autoInckey = -1; ResultSet rs = null; // 获取结果 try { rs = pstmt.getGeneratedKeys(); if (rs.next()){ autoInckey = rs.getInt(1); } } catch (SQLException e) { e.printStackTrace(); } return autoInckey; } //以下方法为使用动态SQL语句方式时,设置prestmt的参数的方法 //其他类型的参数对应的方法,请自行补充 public void setString(int index, String value) { try { pstmt.setString(index, value); } catch (SQLException ex) { System.err.println("Error: " + ex.getMessage()); } } public void setInt(int index, int value) { try { pstmt.setInt(index, value); } catch (SQLException ex) { System.err.println("Error: " + ex.getMessage()); } } public void setLong(int index, long value) { try { pstmt.setLong(index, value); } catch (SQLException ex) { System.err.println("Error: " + ex.getMessage()); } } public void setDouble(int index, double value) { try { pstmt.setDouble(index, value); } catch (SQLException ex) { System.err.println("Error: " + ex.getMessage()); } } public void close() { try { if (stmt != null) { stmt.close(); } if (pstmt != null) { pstmt.close(); } if (con != null) { con.close(); } } catch (SQLException ex) { System.err.println("Error: " + ex.getMessage()); } }}
    2.2.3 业务逻辑层实现(请看service包下的所有(14个)文件。这里只贴首页的控制器代 码IndexServlet.java以及点击投票的代码IndexVoteIdServlet.java)IndexServlet.java
    @WebServlet({"/index","/index/vote/list","/index/vote/search"})public class IndexServlet extends HttpServlet { private static final long serialVersionUID = 7209195686057464382L; protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //由于get传递参数要乱码,所以弄成post,这个问题之后在解决 //System.out.println("搜索投票"); request.setCharacterEncoding("UTF-8"); ItemDao itemDao = new ItemDao(); String path = request.getRequestURI(); List itemList; if (path.indexOf("/search") > -1){ String content = request.getParameter("content").trim(); itemList = itemDao.getSearchItemList(content); request.setAttribute("itemList",itemList); request.setAttribute("active",2); request.getRequestDispatcher("/WEB-INF/view/vote.jsp").forward(request,response); } } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpSession session = request.getSession(); request.setCharacterEncoding("UTF-8"); ItemDao itemDao = new ItemDao(); String path = request.getRequestURI(); List itemList; if (path.indexOf("vote/list") > -1){ session.setAttribute("active",2); itemList = itemDao.getAllItemList(); request.setAttribute("itemList",itemList); request.getRequestDispatcher("/WEB-INF/view/vote.jsp").forward(request,response); }else { session.setAttribute("active",1); itemList = itemDao.getLimitItemList(5);//获取最新五条 request.setAttribute("itemList",itemList); request.getRequestDispatcher("/WEB-INF/view/index.jsp").forward(request,response); } }}
    IndexVoteIdServlet.java
    @WebServlet(name = "IndexVoteIdServlet",urlPatterns = "/index/vote")public class IndexVoteIdServlet extends HttpServlet { private static final long serialVersionUID = -6818273018879095397L; protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpSession session = request.getSession(); int id = Integer.parseInt(request.getParameter("id")); String[] selectIds = request.getParameterValues("answer"); String ip = this.getRemortIP(request); ResultDao resultDao = new ResultDao(); User user = (User)session.getAttribute("user"); int success = resultDao.add(user.getId(),id,selectIds,ip); if (success > 0){ session.setAttribute("msg","投票成功"); response.sendRedirect(request.getContextPath() + "/index/vote/list"); }else{ session.setAttribute("msg","投票失败,请不要用同一ip投票"); response.sendRedirect(request.getContextPath() + "/index/vote?id=" + id); } } @SuppressWarnings("rawtypes") protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpSession session = request.getSession(); session.setAttribute("active",2); int id = Integer.parseInt(request.getParameter("id")); ItemDao itemDao = new ItemDao(); OptionDao optionDao = new OptionDao(); Item item = itemDao.getItemById(id); if (itemDao.getVoteStatus(item.getStartTime(),item.getStopTime()) != 2){ //判断是否可以投票,即投票是否结束,防攻击 response.sendRedirect(request.getContextPath() + "/index/vote/list"); return; } List optionList = optionDao.getOptionList(item.getId(),item.getAllVoteCount()); request.setAttribute("item",item); request.setAttribute("optionList",optionList); String isWaiver="不可以弃权"; String isOppose="不可以投反对票"; System.out.println(item.getIsWaiver()); System.out.println(item.getIsOppose()); if( "1".equals(item.getIsWaiver()) ){ isWaiver="可以弃权"; } if( "1".equals(item.getIsOppose()) ){ isOppose="可以投反对票"; } if( "1".equals(item.getType()) ) { request.setAttribute("type","(单选,"+isWaiver+","+isOppose+")"); }else if( "2".equals(item.getType()) ){ request.setAttribute("type","(多选,可选人数:"+item.getNumber()+","+isWaiver+","+isOppose+")"); }else { request.setAttribute("type","(评分制,可选人数:"+item.getNumber()+","+isWaiver+","+isOppose+")"); } request.getRequestDispatcher("/WEB-INF/view/info.jsp").forward(request,response); } /*获取用户的ip,防止多次投票*/ private String getRemortIP(HttpServletRequest request) { if (request.getHeader("x-forwarded-for") == null) { return request.getRemoteAddr(); } return request.getHeader("x-forwarded-for"); }}
    2.2.4 表现层实现(请查看../WebContent/WEB-INF/view下的所有(14个)文件,这里只贴添 加投票的页面代码:addvote.jsp)<%@ page contentType="text/html;charset=UTF-8" language="java" %><%@include file="head.jsp"%><div class="container-wrap"> <footer> <div class="row copyright" > <div class="col-md-12 text-center"> <big>新增投票</big> </div> </div> </footer></div><!-- END container-wrap --><div class="container-wrap"> <div id="fh5co-contact"> <div class="row"> <div class="col-md-2 col-md-push-1 animate-box"> </div> <div class="col-md-6 col-md-push-1 animate-box"> <form action="${pageContext.request.contextPath}/admin/vote/add" method="post" id="vote_add_form"> <div class="row"> <div class="col-md-12"> <div class="form-group"> <label for="" class="col-sm-3 control-label">主题</label> <div class="col-sm-9"> <input type="text" name="theme" class="form-control" placeholder=""> </div> </div> </div> <div class="col-md-12"> <div class="form-group"> <label for="" class="col-sm-3 control-label">发起人</label> <div class="col-sm-9"> <input type="text" class="form-control" value="${user.realName}" placeholder="" disabled> </div> </div> </div> <div class="col-md-12"> <div class="form-group"> <label for="" class="col-sm-3 control-label">发起时间</label> <div class="col-sm-9"> <input type="text" name="start_time" class="form-control" placeholder="如2018-10-30 15:07:02"> </div> </div> </div> <div class="col-md-12"> <div class="form-group"> <label for="" class="col-sm-3 control-label">结束时间</label> <div class="col-sm-9"> <input type="text" name="stop_time" class="form-control" placeholder="如2018-10-30 15:07:02"> </div> </div> </div> <div class="col-md-12"> <div class="form-group"> <label for="" class="col-sm-3 control-label">投票类型</label> <div class="col-sm-9"> <select name="type" id="" class="form-control"> <option value="1">单选</option> <option value="2">多选</option> <option value="3">评分制</option> </select> </div> </div> </div> <div class="col-md-12"> <div class="form-group"> <label for="" class="col-sm-3 control-label">投票规则</label> <div class="col-sm-9"> <select name="is_waiver" class="form-control"> <option value ="0" selected="selected">不能弃权</option> <option value ="1">可以弃权</option> </select> <select name="is_oppose" class="form-control"> <option value ="0" selected="selected">不能反对</option> <option value ="1">可以反对</option> </select> <input type="text" name="number" class="form-control" placeholder="多选或评分制的可选人数"> </div> </div> </div> <div class="col-md-12"> <div class="form-group"> <label for="" class="col-sm-3 control-label">候选人名单</label> <div class="col-sm-9"> <textarea rows="5" cols="7" name="option_content" class="form-control" placeholder="候选人名之间用按回车键(换行)隔开,如:
陈铭海
李世民"></textarea> </div> </div> </div> <div class="col-md-12"> <div class="form-group"> <label for="" class="col-sm-4 control-label"></label> <input type="button" id="submit1" name="select" value="提 交" class="btn btn-primary btn-modify"> <a href="${pageContext.request.contextPath}/admin"><input type="button" value="返 回 " class="btn btn-primary btn-modify"></a> </div> </div> </div> </form> </div> </div> </div></div><!-- END container-wrap --><%@include file="foot.jsp"%><script type="text/javascript"> $('#submit1').click(function () { var date = /^(\d{4})-(\d{2})-(\d{2})\s+(\d{1,2}):(\d{1,2}):(\d{1,2})$/; var theme = $('input[name=theme]').val(); var start_time = $('input[name=start_time]').val(); var stop_time = $('input[name=stop_time]').val(); var number = $('input[name=number]').val(); var option_content = $('textarea').val(); var type= $('select[name=type]').val(); if(!theme || (theme.length > 255)){ alert('主题不能为空且长度不能超过255个字符'); return; } if(!start_time){ alert('发起时间不能为空'); return; } if(!date.test(start_time)){ alert('发起时间要符合日期格式'); return; } if(!stop_time){ alert('结束时间不能为空'); return; } if(!date.test(stop_time)){ alert('结束时间要符合日期格式'); return; } if(type !="1" && !number){ alert('多选或评分制类型的投票的可选人数不能为空'); return; } if(!option_content){ alert('选项不能为空'); return; } var start_date = new Date(start_time); var stop_date = new Date(stop_time); if (stop_date.getTime() < start_date.getTime()){ alert('结束时间要比开始时间大'); return; } document.getElementById('vote_add_form').submit(); });</script>
    2.3 投票系统的测试2.3.1 黑盒测试(经过测试,发现功能基本完成)安全机制(防止同一ip重复投票,未登录不能投票,非管理员不能进入后台)

    用户登录成功后,查看所有投票活动

    管理员进入后台

    管理员进入后台后,可以添加投票活动

    给新添投票活动投票

    2.3.2 系统测试经过各个系统软件的集合,测试结果显示本软件暂时没有发现bug。
    三、技术讨论3.1 存在问题
    没有实现评分制的投票类型
    解决方案:由于在数据库设计阶段有预留该字段,因此可以一个输入评分框给用户输入来解决
    没有对用户登录次数进行限制
    解决方案:可以在后台对用户每次登录,在session里添加一次,再用ajax和在后台进行测试
    当访问人数多时,可能会卡
    解决方案:可以使用缓存来缓存所有投票活动,或者或者前几天的投票活动,来加快访问速度

    3.2 改进方向
    可以加上安全机制,例如使用 Spring Security框架来加入安全机制
    可以继续开发手机APP或微信小程序这些手机端应用
    可以使用Redis作为缓存加快访问速度
    3 评论 12 下载 2019-04-17 10:42:57 下载需要14点积分
  • 基于python和mysql实现成绩管理系统

    一、需求分析1.1 背景
    软件名称:学生选课与成绩管理系统
    用户:学生,老师

    1.2 任务目标
    提供学生选课与退课功能
    提供老师登入成绩功能
    系统复合实际使用要求,人机界面交互友好,操作方便

    1.3 运行环境python 3.6 mysql
    二、关系模型S(SNO,SNAME,SEX,AGE,SDEPT,LOGN,PSWD)C(CNO,CNAME,CDEPT,TNAME)T(TNO,TNAME,LOGN,PSWD)SC(SNO,CNO,GRADE)
    三、E-R图
    四、项目实现4.1 建立数据库按关系模式写出数据库结构

    4.2 界面设计4.2.1 用户登录页面
    4.2.2 学生选课界面
    4.2.3 教师管理界面
    0 评论 1 下载 2019-06-29 20:45:15 下载需要5点积分
  • python实现的基于源IP加密的传输程序

    一、设计目标1.1 目标概括以对发送数据内容进行隐藏且隐蔽发送方为目标实现一种传输方法,并编写程序实现。
    1.2 实现功能
    基本的数据传输
    通过将发送内容映射到源 IP 地址位置
    修改 IP 以隐蔽发送方
    发送方的 IP 欺骗

    1.3 可加密容量每个数据包的加密容量为 1 个字节,容量比较小,仅适用于传送少量但是重要的信息。
    二、选题原因2.1 网络安全的重要性在网络与信息安全课程中,老师花了一周多的时间对整个网络安全行业进行了介绍。介绍了在网络安全发展的几个阶段中出现的 一些著名的网络安全事件、我国网络信息安全的现状、网络体系的脆弱性。且引用了习近平总书记说过的“没有网络安全,就没有国家安全”,以及介绍了目前的网络安全行业整体产值情况,通过这些方式说明了当前的网络安全行业亟待发展,无论是国家支持程度,还是行业的利润 空间,都表明了网络安全在当前以及未来的重要性。
    2.2 隐蔽通信隐蔽通信是数据传输过程中不可或缺的一部分。数据传输过程中首先不应当直接通过明文传输,然而仅对数据加密在许多情况下并不能满足安全性要求。因此诞生了许多的隐蔽通信技术,不过本质上就是 两点,对数据进行加密、以及对数据所在位置进行隐藏。
    然而信息隐藏的实现重点除了加密算法本身外,还有一个非常重要的问题就是把信息藏在哪里。也就是信息的隐藏载体。信息就算用了非常好的加密算法。如果敌方知道了这段信息就是隐藏后的加密信息。依然可以暴力破解。而如果敌方面对一段信息无法定位真正有价值的信息的位置,则所有的无关信息都可以作为增加对方破解难度的助力。 因此,信息的隐藏载体是研究信息隐藏的重要方向之一。
    2.3 为何使用源 IP 地址进行信息隐藏当下许多研究方案的重点在于通过 TCP/IP 协议中的预留好的部分进行填充来实现隐蔽通信, 例如ICMP发送0~255Bytes 的数据包。但是这些方案有一个问题,那就是如果想要滤除这些信息,可以对防火墙进行设置。这样就可以过滤哪些传送过来的数据包,从而使隐藏的信息丢失。这种方案的问题的本质还是在于容易被敌方探测到隐藏信息的大概位置,即使不可破解,也可以破坏。
    而为何选择用源 IP 地址进行信息隐藏呢?原因如下:

    源 IP 地址即使被修改,也不影响接收方的接收过程,因为源 IP 地址其实就是接收方用于辨别发送者身份的信息。目标 IP 地址自然关乎接收方能否成功接收,而源 IP 地址对接受过程完全不造成影响,可以用来修改以承载隐藏信息
    源 IP 地址是每一个数据包共有的信息,不易引起注意。对于敌方来说,每一个数据包都有其源 IP 地址,源 IP 地址的量非常大,因此增加了处理和分析难度
    对发送者自然而然进行了匿名处理,这种保护是额外的收益

    三、设计实现3.1 问题分析3.1.1 修改源 IP 地址的可行性在选题原因中我已经分析了修改源地址的可行性,可行的原因很简单,就是在数据包传输过程中,路由器不会检查源 IP 地址,知乎检查目标 IP 地址,据此进行中继传输。这就是修改源 IP 地址可行性的理论基础。
    3.1.2 伪造源 IP 的合法性问题TCP/IP 协议在路由过程中不会对源 IP 进行检查,但是出口以及目标局域网入口端的网关和防火墙如果设置了相关规则,则有可能对其进行检查,并筛选掉不符合要求的伪造的 IP 包。因此需要考虑得问题就是伪造的 IP 既要保证携带了隐藏信息,还要能够合法,不被拦截。
    3.1.3 隐藏信息的映射及恢复算法已知一般来说局域网的地址数量不超过 255,所以在对信息量没有过高要求的情况下,可以将信息映射为 8bit,再用这个字节替换源地址,这样对源 IP 的修改量比较小,尽量保证了合法性,且算法实现比较简单。
    3.2 发送方设计3.2.1 传输协议选择——UDP 协议选择 UDP 协议的原因有以下两点:

    TCP 协议需要建立连接,实现三次握手,而此次试验的方法并不需要如此,该设计所能够传输的加密信息上限仅仅是 1 字节每数据包的,设计目的就是短暂通信,传输少量的信息。不需要也不适合建立连接
    该设计方法本质就是修改源 IP。更是不适合 TCP 通信

    3.2.2 隐藏信息映射方法隐藏信息的映射方法实际上有非常强的扩展性,可选择的方式很多,例如 MD5 简化后就可以用于此次试验。不过这个并不是这个实现方法的重点,我认为这个实现的重点在于其通过源 IP 地址 来隐藏信息的思路非常好。这种隐藏方式有迷惑性且不易被拦截,还能起到隐蔽发送方的效果,是比较有价值的地方。考虑到加密后还要恢复的工作量且加密算法本身并不是这个项目的价值体现。所以我只选择了简单的异或的方式进行加密。将源信息每个 byte 和目标 IP 地址的后一个 byte 进行异或,异或结果可以被恢复为源信息,用异或结果替换源 IP 地址后一个 byte。就实现了隐藏信息映射并隐藏。
    3.2.3 数据包序号的意义由于每个数据包能够加密的信息实在是太少,一个信息需要发送许多个数据包来传递,所以为了增强程序的鲁棒性,对数据包进行标号,这样可以方接收方判断是否丢包,是否发送开始,并对数据包进行排序。具体方法为将数据包序号与当前数据包的伪造主机号进行异或,用异或结果替换 MAC 地址的末位byte。
    3.2.4 发送起始和终止标志接收方接收时需要知道包含隐蔽信息的数据包何时开始,何时结束,因此为了实现开始和终止标志,需要额外多发两个数据包表示标志开始序号为 1 作为开始标志,并以此时的加密信息值作为开始和终止标志。加密信息也放在源 IP 地址中。起始和终止的标志数据包的源 IP 地址的内容为真实 IP 的末尾 byte 和目的 IP 地址的末尾 byte 异或得到。
    3.3 接收方设计3.3.1 数据包筛选为了简化算法流程,我规定了发送和接收的源端口和目标端口,少了很多麻烦,只需要对规定端口接收的数据包进行筛选即可。
    3.3.2 解密过程由于加密算法比较简单,完全按照加密过程的逆过程即可。
    判断本次信息接收序列解密信息是否为 1,若是 1,说明是开始标志数据包,其隐藏的信息为真实源 IP 地址的末位 byte。并记录此时的隐藏信息。
    将源 IP 末位 byte 和目的地址的末位 byte 异或得到结果并暂时存储当前接收到的信息,并等待下一个数据包。
    判断每一个收到的数据包加密信息是否为之前记录的信息,当相同时表示终止。
    将数据包的源 MAC 地址的末位 byte 和数据包的源 IP 的末位 byte 异或,得到数据包的序号。
    3.3.3 信息显示若收到结束标志数据包,则进行判断。若结束标志的序号和数据包数量一致,则接收成功,打印接收成功并输出记录好的加密信息和真实的源 IP 地址。
    若结束标志数据包的序号和数据包号表示的长度不一致,打印接收失败。输出真实源 IP 地址,每个数据包的序号以及解密号的信息。
    3.3.4 超时处理为了增加程序完整性,避免空等,引入超时处理机制,若 20秒内没有收到新的数据包,视为超时,停止等待。
    四、实验结果与分析4.1 实验结果截图4.1.1 发送方
    4.1.2 接收方
    4.2 结果分析由于数据包的容量不宜太多,所以我规定的实际上发送方发送的信息为“trap”作为测试。实际上发送的信息只有 4 个字节,按照每个数据包携带一个加密信息作为载体的规则,按说应该有 4 个数据包,但是实际上因为开始标志和结束标志都要作为实际上需要传输的信息,而每个标志需要用一个独立数据包进行传输。因此实际发送的数据包数量为 6。
    可以看到接收方每次接收到一个数据包,对数据包进行逆处理,记录真正的 ip 地址和加密的信息,但是展示的是此时发送给接收方的 ip 地址,也就是发送方想要别人看见的 ip 地址。
    第一次 ip 地址的末尾为 0,为开始标志。此时启动接收方的信息记录程序。每次接收到一个新的数据包,对 ip 末位 byte 进行解密并记 录。直到再接收到一个 ip 地址末尾为 0 的数据包作为结束标志为止。
    当结束接收后,打印真正的存在于开始和结束标志数据包中的真正的 IP 地址,并展示隐藏信息。
    4.3 项目评价与反思4.3.1 优点
    通过多个 IP 地址发送数据,一方面提升了发送者的隐秘性,其次被拦截后攻击者很有可能认为这只是发送者用来隐藏自己的手段,这样就掉入了第二个陷阱,也就是容易忽略 IP 地址的重要 性。仅仅关注数据包本身的内容。这就是对人性的利用
    每次数据包发送的目标传输端口号不同,部分提高了隐蔽性
    通过对数据包进行排序。减少了网络不稳定导致的到达顺序不一致的可能性。增强了程序的鲁棒性
    在开始标志和结束标志数据包中增加了加密后的真实地址的信息,可以选择是否让接收者看见。如果选择让其看见,也可以保证拦截者不看见

    4.3.2 缺点
    加密信息太少,每个数据包仅加密一个字节,因此局限性比较大
    mac 地址用来存储数据包实际的发送序列好,如果 mac 地址不同,但是 IP 地址相同比较可疑(不过这个问题的解决比较容易,并不能算是一个大问题)
    比较取巧,如果拦截方知道了真实的加密策略,拦截很容易

    五、实验心得与感悟5.1 收获本次实验,我手写了一个安全隐蔽传输程序,该程序实现了通过将一段加密信息分布存储在多个数据包的源 IP 地址处,既实现了信息隐藏,又对发送者进行了隐匿。且这种隐匿方式可以做到对接受者可见但是对路由过程中的窥探者进行隐藏。相当于一举三得。在此次实验中,我学习了很多 scapy 库的相关 API 的调用。UDP 协议的包结构以及亲自实现了发送和接收的整个流程。收获良多。
    除此之外,这次实验也是对大三上的上半学期的许多课程学习内容的一次融会贯通,包括计算机网络,网络与信息安全,还有一点的操作系统课程设计方面学到的知识。实验覆盖面广,与课程内容联合紧密。 且有新颖性。
    我认为这种隐藏方法的具体实现并不高深,只是非常新颖。而且有价值的地方在于它利用到了人性。这也和网络与信息安全课上老师强调的内容是一样的。老师讲到了网络攻击与防御发展到后就是人性的思考与判断。提到了攻击者需要站在防御者的角度思考下手点,反之亦然。
    5.2 课程感想老师网络与信息安全这门课程非常系统,前两周老师花了许多精力重点介绍了当前网络完全这个行业的现状和这一学科的重要性和潜力。讲述了行业的发展情况。这促使我们在学习具体内容时能够时刻在脑海中有一个整体的框架,且端正了我们的学习态度。
    此外,老师在理论方面教授了网络安全的基础,也就是密码学。以密码学为基础讲授了网络安全的攻防策略和相关的具体技术。有概念性质的防火墙技术,病毒,等各种概念,也有实践性质的缓冲区溢出以及漏洞利用工具的使用。老师的课程为我们搭建起了整个网络安全学科的整体架构,打开了信息安全的大门。其中具体的内容还需要自己在未来进一步填充。
    1 评论 2 下载 2019-06-28 16:44:20 下载需要8点积分
  • 基于Java2D和Java3D的图形编辑系统

    摘 要基于本学期计算机图形学课程的理解以及课外查找的资料内容,实现了一个基于Java2D及Java3D的图形编辑系统,可以供用户实时交互。基本功能包括二维图形的输入、编辑(裁剪+二维变换)、图像存储以及三维图形(.off文件)的载入。应用技术内容包括各个二维图元的生成算法、变换算法、图像裁剪算法、off文件读取算法以及Java2D/3D编程与API应用技术。整个工程遵循面向对象的设计原则,开发过程具有典型性。文档中注明的[x]在文档末尾,是该算法或过程参考的内容。
    关键词:面向对象;Java2D/3D;二维图形;三维图形;图形化系统;交互
    1 引言计算机图形学研究关于计算机图形对象的建模、处理与绘制等方面的理论和技术。其基本目标是:构建图形对象的虚拟世界,并按特定视角将虚拟模型的场景载图形设备上绘制出来。
    图形系统一般由建模组件和绘制组件两大部分组成,建模组件负责虚拟世界模型的构建,绘制组件完成场景的绘制。本次工程的核心也就是完成各类图形的建模并通过合适的绘制组件使其在屏幕上输出出来。
    图形编程几乎在计算机体系结构的每个层次都留下了自己的身影。它的大致发展趋势是,从平台相关的底层方法向高层抽象的可移植环境发展。所包含的内容也远大于本工程的范畴。不过,可以从本工程开始,窥见一些基础的图形编程理念。
    2 工程结构概述总工程分为两个大方面:二维图形与三维图形。能力有限,并没有能够将这两方面功能结合在一个程序中,下面也将分两部分来介绍工程结构。
    2.1 二维图形工程工程UML框架如下:

    介绍工程所包含的各个类:

    MyItem及形如MyXX的它的子类:定义了所需要绘制的各类二维图形的基本建模(也就是绘制算法)
    CutFrame类:定义了裁剪边框的建模,实际上裁剪边框是一个四边形,它的实现部分继承了四边形图元
    BoundaryFill类:定义了区域填充算法的建模
    TestItem类:程序Main函数所在类,定义了GUI框架的显示以及各个UI控件与类之间的响应,包含了各个用户可使用的按钮的接口实现,也实现了鼠标监听事件,是工程的主类
    Rotate接口:用于实现图元的旋转操作
    Contain接口:用于判断某个点是否在图元内部(配合实现选中图元操作)
    Zoom接口:用于实现图元的缩放操作
    Construct接口:用于实现图元的基本建模(绘制图形)

    一个基本的操作流程及其背后的函数调用如下:

    系统的主要类TestItem介绍:
    主要部分为鼠标监听事件,它统筹管理了所有的事件,用一个pressIndex来管理系统的状态机。
    //5种状态:0:无任何操作 1:选中图元 2:选中图元端点 3:开始画图 4: 填充区域int pressIndex = 0;
    下面是系统的状态转换图:

    2.2 三维图形工程三维图形加载实现在Load3D类中,类包括了一个简单的GUI框架以及一个Button用来链接加载文件的方法,调用该方法成功后,会在屏幕中显示三维模型。
    一个基本的操作流程及其背后的函数调用如下:

    3 主要原理及测试结果展示3.1 直线生成算法直线生成采用中点画线算法[1],具体算法过程如下:
    对于一条直线,可以从鼠标的操作中获取到直线的两个端点,从而计算出直线的斜率k。
    对于k的取值,我做出如下讨论:

    k趋向于无穷时
    k = 0时
    0 < k < 1时(典例)
    其余的k值情况

    前两种情况下不作赘述,主要研究后两种取值。
    以0 < k < 1为例,画线算法的思想如下:
    从直线方程(隐函数)F(x,y)入手
    F(x,y)=ax+by+c.由
    y = (y1-y0)/(x1-x0)•x + C(C为常量)联合上式可以解得:
    a = y0 - y1, b = x1 - x0, c = x0y1 - x1y0.直线上方的点将满足F(x, y) > 0, 直线下方的点将满足F(x, y) < 0。
    对于0 < k < 1的直线,情况如下图:

    起点为(xi, yi)时,Q表示直线上的点,M表示下一格像素的中点。
    将M点坐标代入并将求得的F(x, y)的值记为d。
    求得d > 0时,表示M在直线上方,下一个画的点应选择为下方的P1点。
    此时再往下求下一个点的F(x, y)值,记为d1.

    d1 = F(xi + 2, yi + 0.5) = F(xi + 1, yi + 0.5) = d + ad的增量det1为a.
    反之d < 0时,表示M在直线下方,下一个画的点应选择为上方的P2点。
    此时
    d1 = F(xi + 2, yi + 1.5) = d + a + bd的增量det2为a + b。
    又考虑
    d0 = F(x0 + 1, y0 + 0.5) = a + 0.5b为满足整型要求,取d初值为2a + b,增量
    det1 = 2a, det2 = 2(a + b)代码如下:
    if(k > 0 && k <= 1){ d = 2 * a + b; det1 = 2 * a; det2 = 2 * (a + b); while(x < endx){ if(d < 0){ x++; y++; d += det2; }else{ x++; d += det1; } shape.lineTo(x, y); }}
    再来考虑剩下的情况。如下图:

    可以将直线所在坐标区域分为8个部分。实际上,上述算法只画出了区域1内的直线。
    不过有了0 < k < 1的情况,其余情况都可以比较轻松地推导得出。
    首先,为方便起见,本算法中规定了endx必为x0、x1中较大的一方,从而只需实现第一、第四象限内(即1,2,7, 8区域)的直线处理算法即可。
    区域2:k > 1
    可求得此时
    d = a + 2b, det1 = 2b, det2 = 2 (a + b)else if(k > 1){ d = a + 2 * b; det1 = 2 * b; det2 = 2 * (a + b); while(y < endy){ if(d < 0){ y++; d += det1; }else{ x++; y++; d += det2; } shape.lineTo(x, y); }}
    区域8:-1 < k < 1
    可求得此时
    d = 2a - b, det1 = 2b, det2 = 2(a + b)else if(k >= -1 && k <= 0){ d = 2 * a - b; det1 = 2 * a; det2 = 2 * (a - b); while(x < endx){ if(d < 0){ x++; d += det1; }else{ x++; y--; d += det2; } shape.lineTo(x, y); }}
    区域7:k < -1
    可求得此时
    d = a - 2b, det1 = -2b, det2 = 2(a - b)else if(k < -1){ d = a - 2 * b; det1 = (-b) * 2; det2 = 2 * (a - b); while(y > endy){ if(d < 0){ x++; y--; d += det2; }else{ y--; d += det1; } shape.lineTo(x, y); }}
    下面测试四种情况下直线生成的正确性(展示的是用已实现的保存画布接口存下的画布,逐步添加四条不同直线):
    k > 1

    0 < k < 1

    -1 < k < 0

    k < -1

    测试四种情况均正常。
    除此以外,直线是可以通过端点来进行编辑操作的,技术依赖于鼠标监听事件中实现的实时重绘。
    一个拖动端点来改变直线的测试如下:

    拖动右侧端点至新的位置:

    测试结果正常。
    3.2 圆、椭圆生成算法圆的绘制采用中点圆算法[2],具体过程如下:
    对于一个已知圆,它的参量可以决定出圆上的点满足方程f(x, y) = 0。
    在判断下一个画的点的位置时,可能的两个点的中点坐标代入f(x, y)求值,记为P。

    若P≥0,则表示中点在圆外,应选择靠内侧的点
    若P<0,则表示中点在圆内,应选择靠外侧的点

    如下图所示:

    以此类推,更新P的值。由于圆的对称性,只需迭代至八分之一圆周,将其余坐标通过对称操作得到即可。
    具体算法如下:

    P(0) = 5/4 - r
    求P(k) = f(x + 1, y - 1/2)
    若P(k)<0,取下一个点为(x + 1, y),有递推公式:P(k+1) = P(k) + 2x + 3,重复步骤2直至x > y
    若P(k)≥0,取下一个点为(x + 1, y - 1),有递推公式:P(k + 1) = P(k) + 2x -2y + 3,重复步骤2直至x > y

    算法伪代码(参量已做过整数化处理,适应像素为整数):
    int p = 1 - r; int px = 3, py = 5 - 2 * r;//p即p(k),px,py分别对应p不同取值下的增量while(x <= y){ if(p < 0){ p += px; px += 2; py += 2; x++; //求8个对称点 }else if(p >= 0){ p += py; px += 2; py += 4; x++; y--; //求8个对称点 }}
    此外,需要注意的是,我的算法根据鼠标移动来得到初始画圆的两个端点,所以需要加一步判断两端点的关系,将x坐标较小的一方作为起始点,如下。
    if(a0 < a1){//a0,a1,b0,b1存放修正后的两端点坐标 if(b0 < b1){ ... }else{ ... }}else{ if(b0 < b1){ ... }else{ ...//四处都是在交换对应的坐标位置来保证x坐标较小一方为起始点 }}
    测试画圆功能(显示的两个点为选中该圆后生成的控制点):

    结果正常。
    椭圆的绘制采用中点椭圆生成算法,本质的思想与中点圆生成类似。不同之处在于,由于椭圆的对称性与圆不完全相同,椭圆需要画出至少四分之一的圆周。
    且在切线斜率为1处要进行转换,将按x轴步进改为按y轴步进(若长轴在y轴则相反),以此增加精确度。
    (这里非常坑,在根据书上的步骤实现画图时,发现总是画出畸形的椭圆,比如末端变成直线什么的,断点查了一天之后发现,书上的公式印错了。。。一处少了一个平方项,一处对于P值的判断把≥和<写反了)
    每一步对于下一个点的判断过程会由于分段而略有不同,将在算法中具体介绍。
    下面是纠正过后的算法过程:
    P1(0) = ry^2 - rx^2ry + rx^2/4(开始对区域一进行绘制)

    求P1(k) = f(x + 1, y - 1/2)
    若P1(k)<0,取下一个点为(x + 1, y),有递推公式:P1(k + 1) = P1(k) + 2ry^2x + 3ry^2,重复步骤2直至xry^2 > yrx^2。
    若P1(k)≥0,取下一个点为(x + 1, y - 1),有递推公式:P1(k + 1) = P1(k) + 2ry^2x - 2rx^y + 2rx^2 + 3ry^2,重复步骤2直至xry^2 > yrx^2.(切线斜率经过-1)

    P2(0) = ry^2(x + 1/2)^2 + rx^2(y - 1)^2 - rx^2ry^2(开始绘制区域2)

    求P2(k) = f(x + 1/2, y - 1)。
    若P2(k)>0,取下一个点为(x, y - 1),有递推公式:P2(k + 1) = P2(k) - 2rx^2y + 3rx^2, 重复步骤6直至x > rx。
    若P2(k)≤0,取下一个点为(x + 1, y - 1),有递推公式:P2(k + 1) = P2(k) + 2ry^2x - 2rx^2y + 2ry^2 + 3rx^2, 重复步骤6直至x > rx。
    实现的伪代码如下:
    while(ry * ry * x < rx * rx * y){ if(p1 < 0){ p1 = p1 + 2 * ry * ry * x + 3 * ry * ry; x++; //求四个对称点 }else{ p1 = p1 + 2 * ry * ry * x - 2 * rx * rx * y + 3 * ry * ry + 2 * rx * rx; x++; y--; //求四个对称点 }}int p2 = ry * ry * (x + 1/2) * (x + 1/2) + rx * rx * (y - 1) * (y - 1) - rx * rx * ry * ry;while(x <= rx && y >= 0){ if(p2 > 0){ p2 = p2 - 2 * rx * rx * y + 3 * rx * rx; y--; //求四个对称点 }else{ p2 = p2 + 2 * ry * ry * (x + 1) - 2 * rx * rx * (y - 1) + rx * rx; x++; y--; //求四个对称点 }}
    测试生成长轴在x轴与长轴在y轴的两个椭圆:

    结果正常。
    此外,圆和椭圆均可以通过改变控制点的位置来实现编辑操作,实现原理同直线的编辑操作。
    测试编辑一个圆和一个椭圆的操作如下:

    拖动右侧端点改变圆:

    拖动右侧端点改变椭圆:

    测试结果正常。
    3.3 区域填充算法区域填充是指从区域内的某一个象素点(种子点)开始,由内向外将填充色扩展到整个区域内的过程。
    区域是指已经表示成点阵形式的填充图形,它是相互连通的一组像素的集合。
    区域填充算法(边界填充算法和泛填充算法)是根据区域内的一个已知象素点(种子点)出发,找到区域内其他象素点的过程,所以把这一类算法也成为种子填充算法[3]。
    本算法中实现的是边界填充算法,下面论述一下算法的可行性,并解释伪代码。
    本次实现中将一个Image,或者说是我们所处理的画布视为像素4-连通,也就是说,当我们获取到一个像素的时候,视为这个像素仅与它的上下左右四个方向的四个像素是连通的(重点区分于8-连通)。我们可以通过下面这几张图来更好的理解。




    本次工程设计的图形模式都较为简单,所以采用了像素4连通区域的显示模式。
    首先的问题是填充算法需要访问到当前鼠标所指像素点的RGB值,原本采用过robot方法来截取屏幕获得当前画布,但后来发现robot方法的系统坐标系与我们的画布坐标系是不一样的,故此方法流产。发现到该问题后找到的解决办法是将当前画布存为一个bufferedImage对象,然后从bufferedImage中读取像素,这也成为了后来保存当前画布算法的基础[4][5]。
    将画布转为bufferedImage的主要方法(paintAll()功能为将画布主体的内容都作为一个整体刷到bufImage中):
    TestItem.getTi().paintAll(bufImage.getGraphics());public Color getPixel(int x, int y){//获得当前坐标颜色信息 int rgb = bufImage.getRGB(x, y); Color color = new Color(rgb, true); return color;}
    算法的基本思想是采用递归,过程如下:

    根据鼠标当前所指示的位置来获取当前这个所表示的像素值
    将此点作为种子开始递归算法
    选中一个点,该点是上一个选中点的4连通区域内的一个未被访问过的点
    判断该点的颜色,若与边界颜色或填充色不同,则将该点的颜色(RGB值)设置为填充色
    返回第三步直至没有点可以选择

    不过这种算法是有一定缺陷的,本人在实验过程中就遇到了这种麻烦:栈溢出。因为递归过程是像素级的,所以很容易就会递归太多次,导致栈的大小超过可以承受的量,系统报错。
    解决方法:使用栈来存储点,变递归为循环,减少系统栈空间使用量。缺点是用时可能会久一点。
    修改后的算法过程如下:

    第1步与原过程相同
    将种子点入栈,开始循环
    栈顶元素出栈,检测该点的RGB值,若符合条件则填充
    查找该点的相邻4个点,若没有被入栈过且不为边界色或填充色,则将该点入栈
    循环当栈为空时结束

    具体代码如下:
    public void boundaryFill4() { pStack.push(new PointNode(paintSeedx, paintSeedy)); shape.moveTo(paintSeedx, paintSeedy); while(!pStack.empty()){ PointNode node = pStack.pop(); pointStack[nodeNum].nodex = node.nodex; pointStack[nodeNum].nodey = node.nodey; nodeNum++;//pointStack用来记录被访问过的点 //判断是否符合条件,不是则continue setPixel(node.nodex, node.nodey); PointNode[] pNode = {new PointNode(node.nodex - 1, node.nodey), new PointNode(node.nodex + 1, node.nodey), new PointNode(node.nodex, node.nodey + 1), new PointNode(node.nodex, node.nodey - 1)}; for(int i = 0; i < 4; i++){ //判断是否符合条件,不是则continue Color thisColor = getPixel(pNode[i].nodex, pNode[i].nodey); if((thisColor.equals(boundColor)) || (thisColor.equals(fillColor)) || (thisColor.equals(cutColor))){ continue; } if(inPointStack(pNode[i].nodex, pNode[i].nodey)){ continue; } pStack.push(pNode[i]); } }}
    要注意的是判断颜色时有一个大坑,就是Color类的对象无法用‘==’来进行相等判断,不知道原类是怎么重载这个符号的,只能使用.equals()方法来判断颜色是否相同[6]。
    测试边界填充算法(填充选择了三个封闭区域):

    测试结果正常。
    3.4 图形的平移、旋转、缩放算法平移
    平移算法的实现实际上体现在鼠标监听事件中。
    上面已经说过,在整个系统工程运行的过程里,会有一个pressIndex来管理当前系统的状态机。若选中当前图形,则pressIndex=1。
    在系统处于选中图元的状态时,会监听鼠标的press\drag\release操作,过程如下:

    监听到press,记录当前鼠标位置,为oldPoint
    监听到drag,记录当前鼠标位置,与oldPoint计算平移向量,对图元的控制点进行向量变换,记录为tmpSelected重构图元并重绘
    监听到release,记录当前鼠标位置,与oldPoint计算平移向量,对图元的控制点进行向量变换,重构图元并重绘

    这样保证了移动中图元也能正确地生成。也即实现了平移算法。
    计算平移向量时,得到修改向量的过程如下:
    Point point = new Point(e.getX(), e.getY());int lenx = point.x - oldPoint.x;int leny = point.y - oldPoint.y;
    将lenx与leny加到图元的控制点上即可。
    测试移动一个圆:

    移动后:

    旋转
    旋转算法实现在鼠标的右键菜单中,会生成一个新的窗口来接受用户输入的旋转角度,然后根据该角度将图元进行顺时针旋转(实际上也是对图元的控制点的向量变换,生成变换矩阵并与之相乘)。
    对于任意基准位置的旋转算法如下图:

    在实现过程中,基准点设置为各控制点的中心,也即图元中心点。
    以四边形的旋转为例,展示四边形的rotate接口:
    @Overridepublic void rotate(double angel){ int midx = (x0 + x1) / 2; int midy = (y0 + y1) / 2; shape = new GeneralPath(Path2D.WIND_NON_ZERO); for(int i = 0; i < 4; i++){ double newx = midx + (points[i].getX() - midx) * Math.cos(angel/180 * Math.PI) - (points[i].getY() - midy) * Math.sin(angel/180 * Math.PI); double newy = midy + (points[i].getX() - midx) * Math.sin(angel/180 * Math.PI) + (points[i].getY() - midy) * Math.cos(angel/180 * Math.PI); points[i] = new Point2D.Double(newx, newy); } for(int i = 0; i < 4; i++){ shape.moveTo(points[i].getX(), points[i].getY()); shape.lineTo(points[(i + 1) % 4].getX(), points[(i + 1) % 4].getY()); }}
    测试旋转一条Bezier曲线90°的几个步骤(4个蓝点为图元的控制点):


    输入角度点击确定:


    测试结果正常。
    缩放
    缩放算法也实现在鼠标的右键菜单中,用户输入缩放的倍数(倍数须大于0,大于1时为放大,小于1时为缩小)。输入后按下确定按钮会调用图元的Zoom接口,对图元的控制点进行二维变换操作。
    缩放的基准位置也是图元中心,具体的变换操作如下:
    @Override public void zoom(double power){ int midx = ... int midy = ...//计算得到中点位置 x0 = (int)(midx + power * (x0 - midx)); y0 = (int)(midy + power * (y0 - midy)); ...//其余点也进行同样的操作 this.constructShape();//重新构建图形的模型}
    测试缩放一条bezier曲线至原大小的一半的具体过程:


    输入缩放倍数按下确定:


    测试结果正常。
    3.5 三次Bezier曲线生成算法本工程实现的曲线绘制算法生成的是3次贝赛尔曲线,该曲线有4个控制点,分别为起始点、终止点和两个锚点。下面通过讲解2次贝塞尔曲线的生成来拓展至3次贝赛尔曲线[7]。

    2次贝塞尔曲线的起始点为A,终止点为C,锚点为B。

    先取AB上的一个点D,并在BC上找到点E使得AD:AB = BE:BC
    连接DE,在DE上找到点F使得AD:AB = DF:DE
    使点D从A移动到B,连接这过程中生成的所有F点

    F点的轨迹即ABC三点生成的2次贝赛尔曲线。
    下面讨论n次曲线的生成。
    对于n次贝塞尔曲线来说,F点位置向量的取值有如下公式:

    u的值就是2次曲线中的AD:AB的值,r为生成的曲线点的次数,i为采用的控制点。
    每次要计算F点的向量值时,从r=0的控制点处启动,第一次循环得到若干个1次曲线点,第二次循环得到若干个2次曲线点,循环计算直至r=3,也即生成点在3次曲线上,包含了4个控制点作为参量。
    生成过程如下:

    将4个控制点信息导入一个数组
    对u的取值做遍历,每次递进0.002直至为1
    计算当前所得的F点以及下一个差值为0.001的F点的坐标

    r从0开始递进到3i从0开始递进到3 - r每次循环得到新的P(i)向量为(1-u)*P(i) + uP(i+1)最终得到的P(0)实际上是3次坐标下的P(0),即迭代过三次,就是要求的F点坐标
    得到返回值,连接两个点,回到步骤2

    具体的计算代码如下:
    //每次递进u值,计算F点private Point2D cubicBezier(double u, Point2D[] p) { Point2D[] temp = new Point2D[p.length]; for (int k = 0; k < p.length; k++) temp[k] = p[k]; for (int r = 0; r < 3; r++) { for (int i = 0; i < 4 - r - 1 ; i++) { double x = (1 - u) * temp[i].getX() + u * temp[i + 1].getX(); double y = (1 - u) * temp[i].getY() + u * temp[i + 1].getY(); temp[i] = new Point2D.Double(x,y); } } return temp[0];}private void drawBezier(Point2D[] p) { for (double u = 0; u < 1; u += 0.002) { Point2D p1= cubicBezier(u, p); Point2D p2 = cubicBezier(u + 0.001, p); shape.moveTo(p1.getX(), p1.getY()); shape.lineTo(p2.getX(), p2.getY()); }}
    一开始绘制的贝赛尔曲线是一条直线,由于设定两个锚点的默认值是线段的三等分点,需要拖动锚点或两个端点来改变线的形状实时重绘才能展现出曲线的特性。
    下面测试生成一条贝赛尔曲线并对其端点进行调整。
    曲线生成:

    调整锚点:

    调整端点:

    测试结果正常。
    3.6 裁剪算法裁剪算法实现的是线裁剪,然后对于所有图元都调用了该裁剪的方法。
    以一条直线为例,裁剪的过程如下:

    生成裁剪边框
    检测直线上的点与裁剪边框之间的关系

    点在边框内,保留点在边框外,删除该点(实际上改变了该点的颜色),重构直线的端点(对于其他图元,例如圆,重构端点会导致形状改变,故此类图元只是将该部分的点的颜色值改变),重绘
    重复步骤2直至没有可检测点

    算法伪代码如下:
    private void cut(MyItem Item) { for (选择一个Item上的点) { if(!Point_in_cutFrame){ setPixel(Point); //改变控制点 } }}
    裁剪边框的生成本质上继承了四边形的生成算法,在添加时将其视为一种图元,但在显示时会单独显示,与图元分开。
    为了区别于图元并便于识别,裁剪边框的颜色设置为青色,同时,在区域填充算法中也将裁剪边框的颜色加入限制条件,使得填充算法不会出现错误。
    测试裁剪一个已绘制好图形的画布:
    原画布:

    添加裁剪边框:

    测试结果正常。
    特别地,裁剪边框被设定为只能出现一个,当在已有裁剪边框时再次点击裁剪按钮,原裁剪边框会消失,图形会恢复之前的状态。
    原理是使用了一个单独的CutFrame变量来存储裁剪边框,对于CutFrame是否为空进行判定来判断绘制时是否进入裁剪函数。每次进行过后都会重绘。
    再次按下裁剪按钮后:

    测试正常。
    3.7 保存当前画布上文提到过,TestItem类中包含了一个BufferedImage来存储当前画布的所有内容,具体实现为panelImage对象。每当要存储时,会调用一个printAll函数来将TestItem创建的对象中的所有内容刷到该bufferedImage中,然后将该Image输出为result.png在工程目录下。TestItem实际上继承自JPanel,故可以调用printAll()方法。在保存成功后,控制台会有Catch!字样输出。上述的测试结果中,仅含画布的结果都是通过saveImage()方法存储的,故不再展示测试结果。实际操作时,用户只需按下保存当前画布即可完成操作。
    //保存图片至本地public void saveImage() { JComponent tmpImage = (JComponent)ti; tmpImage.printAll(ti.panelImage.getGraphics()); try { ImageIO.write(ti.panelImage, "png", new File("result.png")); System.out.println("Catch!"); } catch (IOException e) { e.printStackTrace(); }}
    3.8 .off文件读取以及Java3D使用首先明确加载三维模型的过程,步骤如下:

    打开程序,准备加载文件
    读取选择的.off文件,保存其中的点、面信息
    逐步读取面信息,将面包含的各条边相连
    创建Shape3D实例,加入universe中显示为Java3D图形

    加载过程解析:

    打开文件选择器
    若选择的文件不为空,开始读文件
    若首行为OFF,确认为.off文件,准备读取点、面、边数
    根据读到的点数VerticeNum,循环VerticeNum次得到点的信息并存储
    根据读到的面数FaceNum,循环FaceNum次得到面的信息并存储

    主要有难度的算法在于将bufferedReader中获取的readline()字符串中的整型或浮点数提取出[8]。
    算法代码如下(以提取点信息,读取浮点数为例)
    double[][] verticeList = new double[verticeNum][3];for(int i = 0; i < verticeNum; i++){ int cntv = 0; String str = bufferedReader.readLine(); StringBuffer buffer = new StringBuffer(); char[] chars = str.toCharArray(); for(int j = 0; j < chars.length; j++){ char c = chars[j]; if(c != ' '){//浮点数被用空格分隔开,因此检测到空格就可以知道已经读取到一个浮点数 buffer.append(c); } if(c == ' ' || j == (chars.length - 1)){//读到空格或已经到字符串结尾,解析StringBuffer来得到浮点数 double r = Double.parseDouble(buffer.toString()); buffer = new StringBuffer(); verticeList[i][cntv] = r; cntv++; } }}
    在得到所需信息后,开始使用Java3D的画线API来将三维坐标按照面的信息逐个相连。
    代码如下:
    for(int i = 0; i < nof; i++){ for(int j = 0; j < faceVListA[i]; j++){ LineArray lineX = new LineArray(2, LineArray.COORDINATES); lineX.setCoordinate(0, new Point3d(verticeListA[faceListA[i][j]][0], verticeListA[faceListA[i][j]][1], verticeListA[faceListA[i][j]][2]));//[0],[1],[2]分别对应x,y,z轴坐标,需要设置线的起始点和终止点 lineX.setCoordinate(1, new Point3d(verticeListA[faceListA[i][(j + 1) % faceVListA[i]]][0], verticeListA[faceListA[i][(j + 1) % faceVListA[i]]][1], verticeListA[faceListA[i][(j + 1) % faceVListA[i]]][2])); group.addChild(new Shape3D(lineX));//将设置好的线加入group }}
    最后,添加光线和光源的设置,将这些一并添加进创建的universe(Java3D中的特殊对象,可以用于放置三维空间,默认x轴向右,y轴向下,z轴面对屏幕外)空间中,得以显示。
    测试加载一个.off文件:
    选择文件:

    生成三维模型,可该窗口放大来更清楚地观察。

    测试结果正常!
    致谢非常感谢助教在课程群中给出的关于3D建模的视频,其中对于Bezier曲线部分的讲解给了我很大的启发,由此才能想出曲线的正确绘制算法,真的十分感谢!
    参考文献[1] http://blog.chinaunix.net/uid-10976305-id-101132.html 中点画线算法
    [2] https://blog.csdn.net/qq_32583189/article/details/52835856 中点圆算法
    [3] https://blog.csdn.net/soulmeetliang/article/details/79179018 区域填充算法概念介绍
    [4] https://stackoverflow.com/questions/6575578/convert-a-graphics2d-to-an-image-or-bufferedimage bufferedImage的使用
    [5] https://stackoverflow.com/questions/3514158/how-do-you-clone-a-bufferedimage 复制一个bufferedImage
    [6] https://blog.csdn.net/LoHiauFung/article/details/70505739?utm_source=blogxgwz8 Java Color相等判断
    [7] https://www.bilibili.com/video/av35637884 如何设计一个逼真的三维模型(包含Bezier曲线讲解)
    [8] https://blog.csdn.net/u010341602/article/details/40586003 从字符串中提取整数、浮点数(Java)
    [9] https://blog.csdn.net/ubuntu_yanglei/article/details/46443929 获取图片像素点的RGB值
    [10] https://blog.csdn.net/lh\_\_huahuan/article/details/51477503 Java鼠标点击事件
    [11] https://blog.csdn.net/yefufeng/article/details/79947851 生成可执行文件(exe4j教程)
    [12] https://www.cnblogs.com/xugang2008/archive/2009/05/31/1492887.html Java3D安装
    [13] https://stackoverflow.com/questions/48040443/jpanel-button-is-not-at-the-correct-place?r=SearchResults Java Swing JPanel中的布局
    [14] https://blog.csdn.net/ygl19920119/article/details/79723512 Java实现多窗口(弹出新窗体)
    附中文参考文献
    [1] 《计算机图形学教程》机械工业出版社
    [2] 《计算机图形学:应用Java2D和3D》—HongZhang Y.Daniel Liang著 机械工业出版社(特别地,参考了书中前几章对于Java2D程序的实现模板,代码风格,以及后面的二维图形几何变换与裁剪功能的说明)
    1 评论 2 下载 2019-06-28 11:38:31 下载需要11点积分
显示 30 到 45 ,共 15 条
eject