分类

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

资源列表

  • 基于VC++实现的中国象棋-双人象棋游戏

    1 问题描述1.1 要求设计棋盘,棋子等数据,两个玩家可以依次输入数据控制某一个棋子的移动,并且可以判断一步棋是否合法,不合法不能移动,游戏可以存档,读档,记录下棋的过程,如能按棋谱走棋更佳。
    1.2 问题分析1.2.1 象棋记谱规则
    红方从右往左为一至九列(用汉字),黑方从左往右为1至9列(用阿拉伯数字)
    若欲移动的棋子那列没有与该棋子相同的棋子,则记为:

    棋子+列号+动作+步数,其中,列号按6.1的规则产生,动作有进、退、平三种:往对方棋盘方向走为进,往己方棋盘方向走为退,在同一行上移动为平步数:若棋子移动时不改变列,则记为移动的行数(红方汉字黑方阿拉伯数字);若棋子移动时改变列或棋子走斜线,则记为移动到的新列号。例如:
    马三进一 红方第三列的马向黑方棋盘方向走一步,移动到第一列卒3进1 黑方第3列的卒向红方棋盘方向走1步,未改变列车二平五 红方第二列的车走到第五列,未改变行象9退7 黑方第9列的象向己方棋盘方向走一步,移动到第七列

    该列有重子且移动的棋子不为兵、卒,则记为:
    位置+棋子+动作+步数,其中动作和步数的记法与上述相同位置分为前、后两种,在己方前进方向的为前,己方后退方向的为后。例如:
    前炮进一 红方位于棋盘前方的炮前进一步(红方共两个炮,不会混淆)

    该列有重子且移动的棋子不为兵、卒,则记为:
    位置+列号+动作+步数,其中动作和步数的记法与6.2相同位置分为前、中前、中、中后、后五种,望文生义。例如:
    前五进一 红方位于最前方的兵前进一步


    1.2.2 目标
    在控制台界面绘制棋盘和棋子
    设计光标系统,可用键盘方向键控制棋子移动,能用鼠标更佳
    可判断每步棋是否合法,不合法不能移动
    可存档、读档、记录下棋过程
    按棋谱走棋(可选)
    计时、暂停(可选)
    自动判断胜负(可选)
    联机对战(可选)

    1.3 设计描述1.3.1 模块本程序共有六个模块:绘图模块、控制模块、菜单模块、文本与文件模块、DeBug模块、行棋模块。
    1.3.2 各模块的大致功能如下
    绘图模块:提供基础绘图函数(绘制直线、矩形、圆形、文本);绘制棋子、棋盘、光标
    控制模块:根据调用者给定的键值/动作映射表将键盘按键解析成对应动作(如将光标上键解析成光标上移动作 ACT_KEY_UP)
    菜单模块:调用绘图函数绘制游戏功能选择菜单;对菜单选择(键盘按键)做出响应并返回对应动作
    文本与文件模块:实现游戏存档、读档;实现显示下棋过程;实现按棋谱走棋
    DeBug模块:打印程序运行过程中的错误信息,方便调试
    行棋模块:管理棋盘、棋子、光标数据;判断棋子移动是否合法;移动棋子;监控游戏状态;调用绘图函数绘制界面;调用菜单函数显示菜单;调用按键解析函数响应键盘按键;调用文本与文件函数实现存档、读档、显示下棋过程;main函数在该模块中

    1.3.3 模块间的依赖关系如下
    绘图模块依赖行棋模块的常量和数据类型声明(如棋盘行列数、棋盘结构),但不依赖任何模块的函数
    控制模块无依赖项
    菜单模块依赖绘图模块的基础绘图函数;依赖控制模块的键值和行为定义
    文本与文件模块依赖控制模块的游戏状态枚举;依赖行棋模块的常量和数据类型声明;依赖行棋模块的ChessMove函数(实现按棋谱走棋)
    DeBug模块无依赖项
    行棋模块依赖其他所有模块

    1.3.4 文件列表行棋模块

    chess.cpp:主函数及走棋函数实现
    chess.h:棋子棋盘与走棋函数声明

    控制模块

    control.cpp:控制函数实现
    control.h:按键及控制函数声明

    DeBug模块

    debug.cpp:调试与错误处理函数实现
    debug.h:调试与错误处理函数声明

    绘图模块

    graphics.cpp:绘图函数实现
    graphics.h:绘图函数声明
    style.h:界面样式声明

    菜单模块

    menu.cpp:菜单函数实现
    menu.h:菜单函数声明

    文本与文件模块

    text.cpp:文本处理函数实现
    text.h:文本处理函数声明

    辅助头文件

    win32api.h:Windows API 函数声明
    其他文件

    mainpage.h:使用Doxygen生成文档时显示的首页
    1.3.5 枚举棋子类型枚举 enum ChessType,枚举了红黑方的所有棋子类型,包括无棋子。 其中,红方名称中间带_R_,黑方带_K_ (因R和B过于相似,故不用B而用K代表黑方Black)
    枚举值如下所示:
    CHESS_NULL // 无棋子CHESS_R_SHUAI // 红方帅CHESS_R_JU // 红方车CHESS_R_MA // 红方马CHESS_R_PAO // 红方炮CHESS_R_XIANG // 红方相CHESS_R_SHI // 红方仕CHESS_R_BING // 红方兵CHESS_K_JIANG // 黑方将CHESS_K_JU // 黑方车CHESS_K_MA // 黑方马CHESS_K_PAO // 黑方炮CHESS_K_XIANG // 黑方象CHESS_K_SHI // 黑方士CHESS_K_ZU // 黑方卒
    游戏状态枚举enum GameState
    枚举值如下所示:
    GSTAT_ACTIVE // 游戏正在进行GSTAT_PAUSE // 游戏暂停GSTAT_STOP // 游戏结束GSTAT_DRAW // 和棋GSTAT_RED_WIN // 红方玩家获胜GSTAT_BLACK_WIN // 黑方玩家获胜
    玩家枚举enum Player,用来标识玩家是红方还是黑方。 象棋规则中红方先手,黑方后手。
    枚举值如下所示:
    PLY_BOTH // 红方玩家和黑方玩家在同一台电脑上,两者交替操作PLY_RED // 当前为红方玩家PLY_BLACK // 当前为黑方玩家
    动作类型枚举enum ActionType
    枚举值如下所示:
    ACT_UNKNOWN // 未知动作ACT_KEY_UP // 向上移动光标ACT_KEY_DOWN // 向下移动光标ACT_KEY_LEFT // 向左移动光标ACT_KEY_RIGHT// 向右移动光标ACT_KEY_LOCK // 锁定/解锁棋子(锁定棋子再移动光标时棋子也相应移动,再解锁棋子将其放到新位置)ACT_STOP_GAME// 结束游戏ACT_BACK_GAME// 返回游戏ACT_START_GAME// 开始游戏ACT_SAVE_GAME// 保存游戏ACT_LOAD_GAME// 加载游戏ACT_SHOW_MENU// 显示菜单ACT_SHOW_STEP// 查看下棋过程ACT_REFRESH_SCREEN// 刷新屏幕显示ACT_SHOW_ABOUT// 显示关于菜单
    1.3.6 数据结构棋盘结构struct ChessBoard,包括棋盘格子数据、红黑方的位置等。



    char
    map[10][9]





    棋盘格子数据, 一个十行九列的二维数组,前五行为黑方棋盘,后五行为红方棋盘。 (当前玩家是黑方时,虽将黑方绘制在下,但黑方棋盘数据仍在前五行。) 每个数组元素代表棋盘上的一个交叉点,其值为棋子类型枚举值。


    char
    player



    本机玩家,其值为玩家枚举类型,用于联网对战标识玩家,目前无意义。


    char
    activePlayer



    当前活动玩家,其值为玩家枚举类型。该玩家可移动棋子。


    char
    chessLocked



    棋子是否锁定,锁定为1,未锁定为0


    struct ChessPos
    lockedCursor



    锁定棋子的光标位置


    struct ChessPos
    cursorRed



    红方玩家光标


    struct ChessPos
    cursorBlack



    黑方玩家光标


    char
    redChessNum



    红方棋子数


    char
    blackChessNum



    黑方棋子数


    char
    redShuaiExists



    红方帅是否存在,是1否0


    char
    blackJiangExists



    黑方将是否存在,是1否0


    char
    gameState



    游戏状态


    char
    logFileName[100]



    游戏日志文件名称(不包括路径)



    棋子坐标结构struct ChessPos,也用于光标坐标。



    unsigned char
    line





    行号


    unsigned char
    row



    列号



    按键键值结构struct KeyCode



    signed char
    first





    第一字节(键值单字节这里存键值,键值双字节这里存后一个字节)


    signed char
    second



    第二字节(键值单字节这里为-1,键值双字节这里存前一个字节)



    按键状态结构struct KeyState



    char
    ctrlState





    ctrl键状态,0未按下,1按下


    char
    altState



    alt键状态,0未按下,1按下


    char
    shiftState



    shift键状态,0未按下,1按下


    signed char
    lastKey



    存储上一个键。如果按键的键值是双字节则存储按键的首字节。



    1.3.7 行棋模块// 主函数int main(); // 游戏流程控制函数void StartGame(struct ChessBoard *cp); // 初始化棋盘void InitChessBoard(struct ChessBoard *cp, enum Player player);// 移动棋子 char MoveChess(struct ChessBoard *cp, struct ChessPos sourPos, struct ChessPos destPos);
    1.3.8 控制模块// 匹配按键键值与行为char MatchKey (signed char key, struct KeyCode *keycode, int keycodeSize, struct KeyState *keyStat);// 解析按键键值为动作类型char ParseKey(signed char key, struct KeyState *keyStat, char *actionMap, struct KeyCode *keyMap, int actionSize, int keycodeSize);
    1.3.9 绘图模块// 绘制棋盘void DrawChessBoard(struct ChessBoard *cp);// 绘制光标void DrawCursor(struct ChessPos cursor, char player);// 绘制棋子void DrawChess(char chessType, struct ChessPos pos);// 绘制所有棋子void DrawAllChess(struct ChessBoard *cp);// 绘制指定位置的光标void DrawCursorPos(struct ChessBoard *cp, struct ChessPos pos);// 绘制指定位置的棋子 void DrawChessPos(struct ChessBoard *cp, struct ChessPos pos);// 绘制棋盘局部 void DrawChessBoardArea(struct ChessBoard *cp, int line, int row);
    1.3.10 菜单模块// 绘制主菜单并返回用户选择的事件char MainMenuSelect();// 绘制游戏菜单并返回用户选择的事件char GameMenuSelect(); // 绘制玩家获胜游戏菜单 char PlayerWinSelect(char player);//绘制无法存档提示框 void ShowCannotSaveNotice();// 绘制存档失败提示框void ShowSaveFailedNotice(); // 绘制载入存档选择框char LoadSaveSelect(char *title);// 绘制无法载入存档提示框void ShowCannotLoadNotice();// 绘制关于提示框void ShowAbout(); // 查看下棋过程 void ShowStep(struct ChessBoard *cp);
    1.3.11 文本与文件模块// 将棋子走法转换成棋谱char ChessMoveToManual(struct ChessBoard *cp, struct ChessPos sourPos, struct ChessPos destPos, char *result);// 初始化游戏存档目录int InitGameSaveDir()// 写游戏日志 int WriteGameLog(char *fileName, char *content);// 保存游戏int SaveGame(struct ChessBoard *cp, char *fileName);// 加载游戏 int LoadGame(struct ChessBoard *cp, char *fileName);// 解析棋谱(未完成,尚不可用)char ParseMaunal(struct ChessBoard *cp, FILE *fp);
    1.3.12 DeBug模块// 调试与错误处理函数void printErr(char *str);
    2 编码与调试2.1 主界面
    2.2 主函数流程图
    2.3 程序使用说明本游戏为双人中国象棋游戏,由两个玩家在同一台电脑上进行游戏。
    本游戏用键盘操作,暂不支持鼠标。游戏过程中您可以通过菜单查看按键说明。
    在运行ChineseChess.exe后,您将看到本游戏的主菜单,您可以按数字键(主键盘小键盘区均可)选择对应项目:

    2.3.1 新游戏选择“新游戏”后,您将看到一盘已经摆放好的中国象棋,如下图:

    您可以通过键盘上的上、下、左、右方向键或W、S、A、D字母键移动光标(绿色方框)来选择棋子。建议左侧的玩家使用W、S、A、D而右侧的玩家使用方向键。
    选好棋子后,按下空格键或者回车键(小键盘区回车也可)选中棋子。选中棋子后光标会变成红色(若无变化,则选择的不是己方的棋子。下棋过程中红黑两方交替移动棋子,请注意区分)。
    光标变红后再移动,会出现一个新的绿色光标,将其移到希望棋子去的位置,按下空格或回车确认移动。若棋子可以移动至此,则棋子将移动至此,并切换红黑玩家。
    若一方吃掉了另一方的将军(将或者帅),则您会看到玩家获胜提示,如图:

    选择您需要的项即可。
    2.3.2 游戏内菜单游戏进行过程中,您可以按Esc键或F8键弹出游戏内菜单,如图:

    选择您需要的操作即可,该功能也可用于暂停游戏。
    2.3.3 查看下棋过程在游戏内菜单选择“查看下棋过程”可查看用棋谱表示的下棋过程,如图:

    2.3.4 存档并退出在游戏内菜单选择“存档并退出”后,您将看到如下界面:

    您可以按下数字键0到9来选择存档的序号,这也意味着您最多只能保存10个存档。
    若存档成功,该盘棋将自动结束并返回主菜单,若您希望继续棋局,可以从主菜单载入存档。
    若存档失败会有错误提示,并且棋局将不会自动结束。
    2.3.5 载入存档在主菜单选择“载入存档”后,您将看到如下界面:

    输入您存档时所输入的序号即可载入对应的棋局。
    若存档存在,棋局将会恢复到存档时的状态并允许您继续游戏。
    若存档不存在或损坏,您将看到错误提示。
    2.3.6 备注
    由于该游戏不能自动重绘界面,所以当游戏窗口改变大小,或者被其他窗口挡住时,部分或全部画面会消失,这是正常现象,请不要紧张。如果您遇到这种情况,您可以按F5键刷新显示
    游戏存档文件和下棋过程记录文件位于与主程序ChineseChess.exe同目录下的ChineseChess文件夹内,若您需要把游戏连同存档一起转移到其他地方,同时复制ChineseChess.exe和ChineseChess文件夹即可

    2.4 程序验证性测试2.4.1 对局部重绘的验证在StartGame()函数里调用DrawChessBoardArea()函数重绘整个棋盘:
    int i;int j;for (i=0; i<10; i++){ for (j=0; j<9; j++) { DrawChessBoardArea(cp, i, j); }}若棋盘重绘后不正常,说明DrawChessBoardArea()函数未正确实现。
    若棋盘重绘后与重绘前(用全部刷新绘制的)一致,则有理由相信DrawChessBoardArea()函数已正确实现。
    2.4.2 验证结果运行测试代码,发现重绘完后九宫的斜线错位,对DrawChessBoardArea()进行调整后整个棋盘恢复正常,验证成功。
    2.5 对无法存档提示的验证当程序无法创建存档文件夹或者存档文件夹不可写时,程序将在开始运行时显示无法存档提示框。
    为了创造“程序无法创建存档文件夹或者存档文件夹不可写”的情况,在NTFS文件系统中运行该程序,并在ChineseChess文件夹属性的安全选项卡中给EveryOne用户设置拒绝写入。
    2.5.1 验证结果设置拒绝写入权限后,重新运行程序,程序果然弹出无法存档提示框(正常情况下不会弹出)。验证成功。
    3 总结3.1 遇到的问题3.1.1 无法获取控制台句柄使用Windows API的GDI函数绘图需要提供窗口句柄,但是windows.h这没有获取控制台窗口句柄的函数,这样就无法绘图。
    解决
    在网上搜到了GetConsoleWindow()函数,然后创建了一个头文件win32api.h来声明它。
    3.1.3 由于每走一步棋都要刷新一次,所以画面非常闪烁解决
    编写了一个新函数DrawChessBoardArea()用来刷新指定坐标棋子的显示,改全部刷新为局部刷新,仅对有改变的位置进行刷新。
    3.2 最满意的地方我对控制模块的实现非常满意,这使我不需要在每个函数里switch (key),只要提交一个键值/动作的映射表给ParseKey()函数就可以得到用户按键对应的动作了。
    而且这样还能非常方便的支持多字节键值(比如F功能键和光标键就是双字节的,传统的switch() 很难处理),一个动作绑定多个按键,甚至组合键。
    3.3 存在的问题
    因为时间紧张,按棋谱走棋功能最终没有完成,感觉很遗憾
    被其他窗口挡住会造成画面残缺或消失是本程序的一大BUG,但是看上去解决不了了。自己实现窗口重绘消息机制?!
    如果控制台字体大小不是默认值,则游戏启动后窗口的大小会非常奇怪。我花了很久时间解决这个问题但是失败了,因为控制台窗口的逻辑大小是按照字符来计算的,比如我把窗口大小定义为80*42,则窗口的实际大小一行能够显示80个半角字符,共能显示42行。
    但逻辑大小不能决定窗口的实际大小,很显然,若增大控制台字体,窗口的实际大小就会变大,这样棋盘右侧或下方就会出现很多空白的部分,非常难看。我试图用SetWindowPos调整窗口的大小,然后绝望的发现实际大小小于逻辑大小后,窗口会出现滚动条!我只好放弃。

    3.4 收获与感言有人说,控制台程序是没有UI设计的。然而我却看到了虽然是字符界面,但是依然非常精美的vim文本编辑器和elinks网页浏览器。在控制台里(当然,准确来说是终端,linux终端功能比win32控制台强大)可能浏览网页吗?不管人们是否相信,我看到了,elinks做到了。而我今天所做的,也是一个控制台程序。并且毫无疑问,这个控制台程序有UI设计。通过一点一点的查询,我从Windows API的海洋中找到了我需要的所有绘图函数,并且最后真的绘制出了一个像模像样的棋盘,这真是一个奇迹。
    2 评论 5 下载 2018-11-07 16:26:46 下载需要5点积分
  • 基于JSP实现的网上点餐系统

    1 软件项目开发模式
    螺旋开发模式

    适合于项目前期部分需求不确定的情况, 对于每一个模块一个个开发:分析、设计、编码、测试、上线好处: 降低软件风险! (做出的产品要尽量满足客户需求!)
    瀑布模式

    先进行所有模块的需求分析,当分析结束后,才进入项目下一个阶段, 即设计、编码、测试、上线 好处: 更容易进行项目把控,即项目质量控制

    2 需求分析软件工程师: 了解需求的途径?

    需求文档
    项目经理
    项目的系统原型(美工设计师)
    客户

    “餐馆王”系统功能

    餐桌模块
    菜类别模块(菜系)
    菜信息(菜品)
    订单

    详细分析

    后台录入的餐桌, 要在前台首页显示; 且只显示未预定
    后台录入的菜类别, 在前台主页显示
    后台录入的菜信息,在前台主页显示
    前台生成订单后,在后台显示订单详细

    3 数据库设计3.1 创建数据库CREATE DATABASE hotel CHARACTER SET utf8;USE hotel;-- 1. 餐桌表CREATE TABLE dinnerTable( id INT PRIMARY KEY AUTO_INCREMENT, -- 餐桌主键 tableName VARCHAR(20), -- 餐桌名 tableStatus INT DEFAULT 0, -- 餐桌状态:0,空闲; 1,预定 orderDate DATETIME);
    3.2 菜类别表CREATE TABLE foodType( id INT PRIMARY KEY AUTO_INCREMENT, -- 类别主键 typeName VARCHAR(20) -- 类别名称);
    3.3 菜品种表CREATE TABLE food( id INT PRIMARY KEY AUTO_INCREMENT, -- 主键 foodName VARCHAR(20), -- 菜名称 foodType_id INT, -- 所属菜系, 外键字段 price DOUBLE, -- 价格 mprice DOUBLE, -- 会员价格 remark VARCHAR(200), -- 简介 img VARCHAR(100) -- 图片);
    3.4 订单表(订单基本信息)CREATE TABLE orders( id INT PRIMARY KEY AUTO_INCREMENT, -- 主键 table_id INT, -- 外键: 餐桌编号 orderDate DATETIME, -- 下单日期 totalPrice DOUBLE, -- 订单所有菜需要的总金额 orderStatus INT DEFAULT 0 -- 订单状态: 0,未结账; 1,已结账);
    3.5 订单明细表(主要是菜品种)CREATE TABLE orderDetail( id INT PRIMARY KEY AUTO_INCREMENT, -- 主键 orderId INT, -- 外键:引入的是订单表的主键 food_id INT, -- 外键:引用的是菜信息表的主键 foodCount INT -- 菜的数量);
    3.6 添加关系-- 添加菜品与菜类别的关系约束ALTER TABLE food ADD CONSTRAINT fk_food_foodType_id FOREIGN KEY(foodType_id) REFERENCES foodType(id);-- 订单表: 与餐桌表的关系ALTER TABLE orders ADD CONSTRAINT order_table_id FOREIGN KEY(table_id) REFERENCES dinnertable(id);-- 订单明细: 与订单表的关系ALTER TABLE orderDetail ADD CONSTRAINT orderDetail_order_id FOREIGN KEY(orderId) REFERENCES orders(id);-- 订单明细: 与菜信息的关系ALTER TABLE orderDetail ADD CONSTRAINT orderDetail_food_id FOREIGN KEY(food_id) REFERENCES food(id);
    4 系统设计
    开源组件及jar文件:

    数据库驱动包(1个)C3P0连接池包(2个)DbUtils组件 (1个)BeanUtils组件(2个)FileUpload组件(2个)
    配置

    C3p0配置文件
    分层

    Entity/dao/service/servlet
    图解




    2 评论 10 下载 2019-01-04 10:54:55 下载需要6点积分
  • 基于JSP实现的学生成绩管理系统

    1 引言1.1 任务简介
    多用户管理:用户分管理员,学生
    网页界面设计:利用HTML和CSS实现客户端前台设计
    类间关系的设计、数据库表格设计
    数据库数据的增(录入)、删、改、查等基本功能
    JSP中Requests Response内置对象的使用;
    数据库表格结构的生成(SQL脚本)
    前台JS校验设计
    DOM技术实现
    其他扩展功能
    开发环境与技术:IDEA、Java 语言、JDK 1.7、MySQL 6.0

    1.2 需求分析本学生成绩管理系统分为管理员登录,学生登录,其中管理员可以实现增加学生成绩信息,删除学生成绩信息,修改学生成绩信息,查找学生成绩信息,按照学生GPA进行排名,其中学生可以实现登录查询成绩功能,能否成功登录取决于数据库中是否有该学生。
    2可行性分析2.1 社会可行性分析成绩是每一个大学生都会接触到的东西,不仅学生为之心动,为了管理学生的成绩,如果不用计算机来实现,老师们也会感觉很头疼麻烦,开发学生成绩管理系统后,让学生的成绩管理变的更加方便,学生也可以实现自助查询成绩功能,老师们也可以对学生成绩进行排名等。
    2.2 经济可行性分析该系统的开发调查主要是需要软件,这些软件都是免费的。主要的花销在于服务器的维护,除此之外没有其他的花销。
    2.3 法律可行性分析系统开发及维护所使用工具和技术及数据信息不违反法律。
    3系统设计3.1 系统功能设计学生成绩管理系统具备以下功能:管理员学生分权限登录,增加学生成绩信息,删除学生成绩信息,修改学生成绩信息,查询学生成绩,按照学生GPA进行排名。

    分权限登录:系统分为管理员和学生用户两个权限,因为管理员就一个,所以账号秘密固定,但是学生有很多,所以在学生登录的过程中要对数据库中的学生进行查询,如果存在,而且密码正确,才能成功登录
    增加学生成绩:该功能可以将学生的姓名,学号,一系列成绩,登录密码等信息录入系统
    删除学生成绩:该功能可以将学生的信息全部列举出来,然后选择想要删除的学生的信息
    修改学生成绩:该功能可以将学生的所有信息全部列举出来,然后再选择想要修改的学生的信息,然后弹出一个修改框,再做仔细的修改
    查询学生成绩信息:该功能可以在输入学号后显示出学生的各项成绩信息
    按照学生GPA进行排名:该功能可以将所有学生按照GPA进行排名,并显示出来

    3.2 算法流程设计根据需求分析,将本系统分为以下模块,模块图如下:

    4 关键技术及代码实现4.1 基本功能4.1.1 Web前端设计与实现4.1.1.1 HTML关键技术及应用include file技术
    <html> <head> <title> 学生成绩管理系统 </title> </head> <body bgcolor="#FFFFFF"> <%@ include file="include_head_JSP2.jsp"%> </body></html>
    Bootstrap 框架技术
    <%@page contentType= "text/html; charset=UTF-8" language= "java" errorPage=""%> <!DOCTYPE html><html> <head> <style> body{ background-image: url("1281116171866201.jpg"); } </style> <title>学生管理系统</title> <meta name= "viewport" content = "width=device-width, initial-scale= 1.0"> <!-引入Bootstrap-> <link href="bootstrap.css" rel="stylesheet"> </head> <body> <center> <hi>学生成绩管理系统</hi></center> <br> <center> <div class ="container"> <a href= "selectLogin .j sp" class= "btn btn-info" role= "button">管理员</a> <a href= "studentLogin.jsp" class= "btn btn-info" role= "button">学生</a> </div></center> <!– jQuery (Bootstrap 的 JavaScript 插件需要引⼊ jQuery) –> <script src=”jquery-3.2.1.min.js”></script> <!– 包括所有已编译的插件 –> <script src=”bootstrap.min.js”></script> </body> </html>
    4.1.1.2 CSS关键技术及应用Bootstrap.css技术
    大量运用Boostrap前段框架中的CSS样式,美化登录界面,表格,按钮。
    响应式表格:
    <table class=”table table-condensed table-hover” >
    美化按钮:
    <a href=”Findinfo2.jsp” class=”btn btn-info” role=”button”> 返回 </a>
    4.1.1.3 JS校验设计及应用登录界面的校验
    <script language=”javascript” type=””> function checkform() { //验证输⼊数据的合法性 if (form1.teacherName.value==””) { alert(” ⽤户名不能为空。”); return false; } if (form1.teacherPassword.value==””) { alert(” 密码不能为空。”); return false; }}</script>
    往数据库写⼊数据的检验
    function checkform(){ //验证输⼊数据的合法性 if (form1.id.value==””) { alert(” 学号不能为空。”); return false; } if (isNaN(form1.id.value)) { alert(” 学号只能为数字”); return false; } if (form1.name.value==””) { alert(” 姓名不能为空。”); return false; } if (form1.cppstring.value==””) { alert(”C++ 成绩不能为空。”); return false; } if (isNaN(form1.cppstring.value)) { alert(”C++ 成绩只能为数字”); return false; } else { var num=parseFloat(form1.cppstring.value); if (num<0||num>100) { alert(”C++ 成绩必须在 0-100 之间!”); return false; } } if (form1.cirstring.value==””) { alert(” 电路成绩不能为空。”); return false; } if (isNaN(form1.cirstring.value)) { alert(” 电路成绩只能为数字”); return false; } else { var num=parseFloat(form1.cirstring.value); if (num<0||num>100) { alert(” 电路成绩必须在 0-100 之间!”); return false; } } if (form1.Ewritestring.value==””) { alert(” 英语读写成绩不能为空。”); return false; } if (isNaN(form1.Ewritestring.value)) { alert(” 英语读写成绩只能为数字”); return false; } else { var num=parseFloat(form1.Ewritestring.value); if (num<0||num>100) { alert(” 英语读写成绩必须在 0-100 之间!”); return false; } } if (form1.Elistenstring.value==””) { alert(” 英语听说成绩不能为空。”); return false; } if (isNaN(form1.Elistenstring.value)) { alert(” 英语听说成绩只能为数字”); return false; } else { var num=parseFloat(form1.Elistenstring.value); if (num<0||num>100) { alert(” 英语听说成绩必须在 0-100 之间!”); return false; } } if (form1.physicsstring.value==””) { alert(” ⼤学物理成绩不能为空。”); return false; } if (isNaN(form1.physicsstring.value)) { alert(” ⼤学物理成绩只能为数字”); return false; } else { var num=parseFloat(form1.physicstring.value); if (num<0||num>100) { alert(” ⼤学物理成绩必须在 0-100 之间!”); return false; } } if (form1.prostring.value==””) { alert(” 概率论成绩不能为空。”); return false; } if (isNaN(form1.prostring.value)) { alert(” 概率论成绩只能为数字”); return false; } else { var num=parseFloat(form1.prostring.value); if (num<0||num>100) { alert(” 概率论成绩必须在 0-100 之间!”); return false; } } if (form1.hisstring.value==””) { alert(” 近代史成绩不能为空。”); return false; } if (isNaN(form1.hisstring.value)) { alert(” 近代史成绩只能为数字”); return false; } else { var num=parseFloat(form1.hisstring.value); if (num<0||num>100) { alert(” 近代史成绩必须在 0-100 之间!”); return false; } } if (form1.xingzhengstring.value==””) { alert(” 形势与政策成绩不能为空。”); return false; } if(isNaN(form1.xingzhengstring.value)) { alert(” 形势与政策成绩只能为数字”); return false; } else { var num=parseFloat(form1.xingzhengstring.value); if (num<0||num>100) { alert(” 形势与政策成绩必须在 0-100 之间!”); return false; } } if (form1.pestring.value==””) { alert(” 体育成绩不能为空。”); return false; } if (isNaN(form1.pestring.value)) { alert(” 体育成绩只能为数字”); return false; } else { var num=parseFloat(form1.pestring.value); if (num<0||num>100) { alert(” 体育成绩必须在 0-100 之间!”); return false; } } if (form1.discretestring.value==””) { alert(” 离散数学成绩不能为空。”); return false; } if (isNaN(form1.discretestring.value)) { alert(” 离散数学成绩只能为数字”); return false; } else { var num=parseFloat(form1.discretestring.value); if (num<0||num>100) { alert(” 离散数学成绩必须在 0-100 之间!”); return false; } } if (form1.rank.value==””) { alert(” 查询密码成绩不能为空。”); return false; } if (form1.rank.value.length<1||form1.rank.value.length>20) { alert(” 密码超出了范围(1 ~ 20)”); return false; }}</script>
    4.1.1.4 DOM 关键技术及应用DOM 实际上是以面向对象方式描述的文档模型。DOM 定义了表⽰和修改⽂档所需的对象、这些对象的⾏为和属性以及这些对象之间的关系。可以把 DOM 认为是页面上数据和结构的一个树形表示,不过页面当然可能并不是以这种树的方式具体实现。 通过 JavaScript,您可以重构整个 HTML 文档。您可以添加、移除、改变或重排页面上的项目。 例如使用 Dom 技术来进⾏ Js 校验:
    <script language=”javascript” type=””> function checkform() { //验证输⼊数据的合法性 if (form1.teacherName.value==””) { alert(” ⽤户名不能为空。”); return false; } if (form1.teacherPassword.value==””) { alert(” 密码不能为空。”); return false; }} </script>
    4.1.2 Web 后台设计及实现本学生管理系统后台设计使用了 Java 脚本,Java servlet 过滤器,以及 Java 数据库技术等。
    4.1.2.1 数据库设计及 SQL 脚本生成数据库脚本
    CREATE TABLE ‘grade‘ ( ‘id‘ varchar(30) NOT NULL, ‘name‘ varchar(30) NOT NULL, ‘cpp‘ varchar(30) NOT NULL, ‘circuit‘ varchar(30) NOT NULL, ‘Ewrite‘ varchar(30) NOT NULL, ‘Elisten‘ varchar(30) NOT NULL, ‘physics‘ varchar(30) NOT NULL, ‘probability‘ varchar(30) NOT NULL, ‘history‘ varchar(30) NOT NULL, ‘xingzheng‘ varchar(30) NOT NULL, ‘pe‘ varchar(30) NOT NULL, ‘discrete‘ varchar(30) NOT NULL, ‘overall‘ varchar(30) DEFAULT ’0’, ‘gpa‘ varchar(30) DEFAULT ’0’, ‘rank‘ varchar(30) DEFAULT ’0’ )
    4.1.2.2 增删改查功能实现增加学生成绩信息
    String idstring=ChangeEncoding(request.getParameter(”id”).trim()); String namestring=ChangeEncoding(request.getParameter(”name”).trim());String cppstring=request.getParameter(”cppstring”); String cirstring=request.getParameter(”cirstring”); String Ewritestring=request.getParameter(”Ewritestring”);String Elistenstring=request.getParameter(”Elistenstring”); String physicsstring=request.getParameter(”physicsstring”);String prostring=request.getParameter(”prostring”);String hisstring=request.getParameter(”hisstring”); String xingzhengstring=request.getParameter(”xingzhengstring”);String pestring=request.getParameter(”pestring”);String discretestring=request.getParameter(”discretestring”); String overallstring=getOvarall(cppstring,cirstring,Ewritestring,Elistenstring, physicsstring,prostring,hisstring,xingzhengstring,pestring,discretestring); String gpastring=getGPA(cppstring,cirstring,Ewritestring,Elistenstring, physicsstring,prostring,hisstring,xingzhengstring,pestring,discretestring); String rankstring=request.getParameter(”rank”); //构造 SQL 语句 Stringsql=”insertintograde(id,name,cpp,circuit,Ewrite,Elisten,physics,probability,history,xingzheng”+”,pe,discrete,overall,gpa,rank)”+”VALUES(’”+idstring+”’,’”+namestring+”’,’”+cppstring+”’,’”+cirstring+”’,’”+Ewritestring+”’,’”+Elistenstring+”’,’”+physicsstring+”’,’”+prostring+”’,’”+hisstring+”’,’”+xingzhengstring+”’,’”+pestring+”’,’”+discretestring+”’,’”+overallstring+”’,’”+gpastring+”’,’”+rankstring+”’)”; String DBDRIVER = ”org.gjt.mm.mysql.Driver” ; // 定义 MySQL 数据库的连接地址 String DBURL = ”jdbc:mysql://localhost:3306/student”; // MySQL 数据库的连接⽤户名 String DBUSER = ”root” ;// MySQL 数据库的连接密码String DBPASS = ”xzk520521”;try { Class.forName(DBDRIVER).newInstance();} catch (ClassNotFoundException e) { out.print(” 错误”); e.printStackTrace();}try { Connection conn=DriverManager.getConnection(DBURL, DBUSER, DBPASS); Statement stmt=conn.createStatement(); stmt.executeUpdate(sql); out.print(”<center>”); out.println(”<P><font size=2’>”+” 向数据库增加学⽣信息”+”</font>”); out.println(”<P><font size=2’>”+” 该学⽣信息数据已经成功添加到数据库。”+”</font>”); out.print(”</center>”); stmt.close(); conn.close(); } catch(SQLException e) { out.print(” 错误”); e.printStackTrace(); }
    删除学生成绩
    String DBDRIVER = ”org.gjt.mm.mysql.Driver” ;// 定义 MySQL 数据库的连接地址String DBURL = ”jdbc:mysql://localhost:3306/student”; // MySQL 数据库的连接⽤户名 String DBUSER = ”root” ; // MySQL 数据库的连接密码 String DBPASS = ”xzk520521”; String id=codeToString(request.getParameter(”id”).trim());//构造 SQL 语句 String sql=”delete from grade where id=’”+id+”’”; try { Class.forName(DBDRIVER).newInstance(); } catch (ClassNotFoundException e) { out.print(” 错误”); e.printStackTrace();} try{ Connection conn=DriverManager.getConnection(DBURL, DBUSER, DBPASS); Statement stmt=conn.createStatement(); stmt.executeUpdate(sql); out.print(”<center>”); out.println(”<P><font size=2 color=’blue’>”+” 向数据库删除学⽣信息数据”+”</font>”); out.println(”<P><fontsize=2’>”+”学号为:”+id+”的学⽣数据信息已经被成功删除。”+”</font>”); out.print(”</center>”); stmt.close(); conn.close(); }catch(SQLException e) { out.print(” 错误”); e.printStackTrace(); }
    查找学生成绩信息
    String DBDRIVER = ”org.gjt.mm.mysql.Driver” ; // 定义 MySQL 数据库的连接地址 String DBURL = ”jdbc:mysql://localhost:3306/student” ; // MySQL 数据库的连接⽤户名 String DBUSER = ”root” ; // MySQL 数据库的连接密码 String DBPASS = ”xzk520521”; String id=request.getParameter(”id”); String sql=”select * from grade where id=’”+id+”’”;//设置查询 SQL 语句try { Class.forName(DBDRIVER).newInstance(); } catch (ClassNotFoundException e) { out.print(” 错误”); e.printStackTrace(); } try { Connection conn=DriverManager.getConnection(DBURL, DBUSER, DBPASS); Statement stmt=conn.createStatement(); ResultSet rs=stmt.executeQuery(sql); if(!rs.next()) { out.print(”<center>”); out.println(”<P><fontsize=2color=’blue’>”+”该学⽣尚未录⼊系统,请前去录⼊!”+”</font>”); } else{ dec.idField.setText(rs.getString(”id”)); dec.nameField.setText(rs.getString(”name”)); dec.cppField.setText(rs.getString(”cpp”)); dec.cirField.setText(rs.getString(”circuit”)); dec.EwriteField.setText(rs.getString(”Ewrite”)); dec.ElistenField.setText(rs.getString(”Elisten”)); dec.phyField.setText(rs.getString(”physics”)); dec.proField.setText(rs.getString(”probability”)); dec.historyField.setText(rs.getString(”history”)); dec.xingzhengField.setText(rs.getString(”xingzheng”)); dec.peField.setText(rs.getString(”pe”)); dec.discreteField.setText(rs.getString(”discrete”)); dec.overallField.setText(rs.getString(”overall”)); dec.gpaField.setText(rs.getString(”gpa”)); } rs.close(); stmt.close(); conn.close();}catch (SQLException e) { e.printStackTrace(); }
    修改学生成绩信息
    String idstring=ChangeEncoding(request.getParameter(”id”).trim()); String namestring=ChangeEncoding(request.getParameter(”name”).trim()); String cppstring=request.getParameter(”cpp”); String cirstring=request.getParameter(”circuit”); String Ewritestring=request.getParameter(”Ewrite”); String Elistenstring=request.getParameter(”Elisten”); String physicsstring=request.getParameter(”physics”); String prostring=request.getParameter(”probability”);String hisstring=request.getParameter(”history”);String xingzhengstring=request.getParameter(”xingzheng”);String pestring=request.getParameter(”pe”);String discretestring=request.getParameter(”discrete”); Stringoverallstring=getOvarall(cppstring,cirstring,Ewritestring,Elistenstring,physicsstring,prostring,hisstring,xingzhengstring,pestring,discretestring); Stringgpastring=getGPA(cppstring,cirstring,Ewritestring,Elistenstring,physicsstring,prostring,hisstring,xingzhengstring,pestring,discretestring); String rankstring=request.getParameter(”rank”); String DBDRIVER = ”org.gjt.mm.mysql.Driver” ; // 定义 MySQL 数据库的连接地址 String DBURL = ”jdbc:mysql://localhost:3306/student” ; // MySQL 数据库的连接⽤户名 String DBUSER = ”root” ; // MySQL 数据库的连接密码 String DBPASS = ”xzk520521”; String id=ChangeEncoding(request.getParameter(”id”).trim());//构造 SQL 语句 Stringsql=”updategradesetid=’”+idstring+”’,name=’”+namestring+”’,cpp=’”+cppstring+”’,circuit=’”+cirstring+”’,Ewrite=’”+Ewritestring+”’,Elisten=’”+Elistenstring+”’,physics=’”+physicsstring+”’,probability=’”+prostring+”’,history=’”+hisstring+ ”’,xingzheng=’”+xingzhengstring+”’,pe=’”+pestring+”’,discrete=’”+discretestring+”’,overall=’”+overallstring+”’,gpa=’”+ gpastring+”’,rank=’”+ rankstring+”’where id=’”+id+”’”; try { Class.forName(DBDRIVER).newInstance();} catch (ClassNotFoundException e) { out.print(” 错误”); e.printStackTrace();}try { Connection conn=DriverManager.getConnection(DBURL, DBUSER, DBPASS); Statement stmt=conn.createStatement(); stmt.executeUpdate(sql); out.print(”<center>”); out.println(”<P><font size=2 color=’blue’>”+” 向数据库修改学⽣信息数据”+”</font>”); out.println(”<P><fontsize=2’>”+”学号为:”+id+”的学⽣数据信息已经被成功修改。”+”</font>”); out.print(”</center>”); stmt.close(); conn.close();} catch(SQLException e) { out.print(” 错误”); e.printStackTrace(); }
    显⽰——按照学⽣的 GPA 进⾏排名并打印
    <% String DBDRIVER = ”org.gjt.mm.mysql.Driver” ; // 定义 MySQL 数据库的连接地址String DBURL = ”jdbc:mysql://localhost:3306/student” ; // MySQL 数据库的连接⽤户名 String DBUSER = ”root” ; // MySQL 数据库的连接密码 String DBPASS = ”xzk520521”; int realrank=1; String sql=”select * from grade ORDER BY gpa DESC”;//设置查询 SQL 语句 try { Class.forName(DBDRIVER).newInstance();}catch (ClassNotFoundException e) { out.print(” 错误”); e.printStackTrace(); } try { Connection conn=DriverManager.getConnection(DBURL, DBUSER, DBPASS); Statement stmt=conn.createStatement(); ResultSet rs=stmt.executeQuery(sql); %> <center><h1> 学⽣成绩 GPA 排名表 </h1></center> <hr> <table class=”table table-condensed table-hover” > <thead> <tr > <th> 学号 </th> <th> 姓名 </th> <th>C++ 成绩 </th> <th> 电路成绩 </th> <th> 英语读写 </th> <th> 英语听说 </th> <th> ⼤学物理 </th> <th> 概率论 </th> <th> 近代史 </th> <th> 形势与政策 </th> <th> 体育 </th> <th> 离散数学 </th> <th> 总分 </th> <th>GPA</th> <th> 排名 </th> </tr> </thead> <% while (rs.next()){ //获取学⽣数据表中的记录 %> <tr > <td><%=rs.getString(”id”)%></td> <td><%=rs.getString(”name”)%></td> <td><%=rs.getString(”cpp”)%></td> <td><%=rs.getString(”circuit”)%></td> <td><%=rs.getString(”Ewrite”)%></td> <td><%=rs.getString(”Elisten”)%></td> <td><%=rs.getString(”physics”)%></td> <td><%=rs.getString(”probability”)%></td> <td><%=rs.getString(”history”)%></td> <td><%=rs.getString(”xingzheng”)%></td> <td><%=rs.getString(”pe”)%></td> <td><%=rs.getString(”discrete”)%></td> <td><%=rs.getString(”overall”)%></td> <td><%=rs.getString(”gpa”)%></td> <td><%=realrank++%></td> </tr> <% } rs.close(); stmt.close(); conn.close(); } catch (SQLException e){ e.printStackTrace(); } %> </table>
    4.1.3 Java-Serviet设计与实现Java-Serviet技术主要用于过滤器的实现,过滤器就是不能再未登录的情况下直接用URL 地址访问操作界面,用Java-Serviet技术实现如下:
    userLoginCheckFilter.java
    package mywebapp; import java.io.IOException; import java.util.ArrayList; import java.util.List; import javax.servlet.FilterConfig;import javax.servlet.ServletException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession; public class userLoginCheckFilter implements Filter { public static final Stringloginpage = ”index.jsp”; public void destroy(){ } public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)throws ServletException, IOException { HttpServletRequest request = (HttpServletRequest)servletRequest; HttpServletResponse response = (HttpServletResponse)servletResponse; String currentURL = request.getRequestURI(); String ctxPath = request.getContextPath(); //除掉项⽬名称时访问页⾯当前路径 String targetURL = currentURL.substring(ctxPath.length()); System.out.println(targetURL); List<String>info = new ArrayList<String>(); HttpSession session = request.getSession(false); //对当前页⾯进⾏判断,如果当前页⾯不为登录页⾯ if(!((”/index.jsp”.equals(targetURL)) || (”/selectLogin.jsp”.equals(targetURL)) || (”/studentLogin.jsp”.equals(targetURL)))){ //在不为登陆页⾯时,再进⾏判断,如果不是登陆页⾯也没有 session 则跳转到登录页⾯ if(session == null || session.getAttribute(”admin”) == null){ info.add(”You are not logged in!”); request.setAttribute(”info”,info); request.getRequestDispatcher(loginpage).forward(request,response); return; } else{ //这⾥表⽰正确,会去寻找下⼀个链,如果不存在,则进⾏正常的页⾯跳转 filterChain.doFilter(request, response); return; } } else{ //这⾥表⽰如果当前页⾯是登陆页⾯,跳转到登陆页⾯ try{ filterChain.doFilter(request, response); } catch (Exception e){ throw e; } return; } } public void init(FilterConfig filterConfig)throws ServletException{ }}
    web.xml ⽂件
    <?xml version=”1.0” encoding=”UTF-8”?> <web-app xmlns=”http://xmlns.jcp.org/xml/ns/javaee” version=”3.1”><filter> <filter-name>userLoginCheckFilter</filter-name> <filter-class>mywebapp.userLoginCheckFilter</filter-class></filter> <filter-mapping> <filter-name>userLoginCheckFilter</filter-name> <url-pattern>*.jsp</url-pattern> </filter-mapping> </web-app>
    4.1.4 JSP内置对象的使用本成绩管理系统用了4种JSP内置对象,分别是Request、Response、Out、Session。下面分别列出:
    4.1.4.1 RequestString idstring=ChangeEncoding(request.getParameter(”id”).trim()); String namestring=ChangeEncoding(request.getParameter(”name”).trim()); String cppstring=request.getParameter(”cpp”); String cirstring=request.getParameter(”circuit”);String Ewritestring=request.getParameter(”Ewrite”); String Elistenstring=request.getParameter(”Elisten”); String physicsstring=request.getParameter(”physics”);String prostring=request.getParameter(”probability”); String hisstring=request.getParameter(”history”);String xingzhengstring=request.getParameter(”xingzheng”); String pestring=request.getParameter(”pe”); String discretestring=request.getParameter(”discrete”);
    4.1.4.2 Responseresponse.sendRedirect(”teacharMenu.jsp”);
    4.1.4.3 Outout.print(”<center>”); out.println(”<P><font size=2 color=’blue’>”+” 管理员⽤户或密码错误”+”</font>”);
    4.1.4.4 Sessionsession.setAttribute(”teacherName”,name); session.setAttribute(”teacherPassword”,password); String user=(String)session.getAttribute(”teacherName”); String pwd=(String)session.getAttribute(”teacherPassword”);
    4.1.5 多用户管理设计与实现首先进入系统时出现选择用户,如果选择管理员需要知道管理员账号密码,如果选择学生, 需要该学生的学号在数据库中而且密码正确。
    5 系统演示登录

    增加


    删除


    查找


    修改

    排名

    6 总结6.1 系统缺陷与不足
    系统运用的查找算法都是暴力算法,如果面对很多数据的话,系统运行会缓慢,可以运用二分查找算法或者改变存储的数据结构进行优化
    系统编码不够灵活,编码转换方面容易产生乱码

    6.2 系统可扩展功能
    增加选课功能
    增加多个管理员功能
    增加学生自助修改密码功能
    增加一系列功能做成教务系统
    3 评论 37 下载 2018-11-05 12:31:35 下载需要6点积分
  • 基于JAVA的聊天室

    聊天室系统
    一、课程设计要求与目的目的:编写一个小型Java聊天室系统,掌握Java网络通信、多线程、IO文件操作等高级应用编程技能。
    完成如下功能:

    多客户端模式下,实现客户与客户的单独通信,要求信息通过服务器中转
    端到端的通信,实现并行通信模式(不再是你说一句,我说一句,一端的信息发送不受另一端的影响)
    实现端到端的文件传输
    添加图形界面(选做)

    二、系统设计服务器端:分为两个class文件,一个是MultiTalkServer类,还有一个是ServerThread类。MultiTalkServer实现的功能有:监听客户请求,根据得到的Socket对象和客户计数创建服务线程,并启动。将serverSocket.accept()返回的socket作为参数,传入到ServerThread中。
    ServerThread实现的功能有:判断是否下线,判断IP地址是否正确,判断目标客户是否在线,判断是否传输文件,获取目标的socket套接字。

    三、系统实现服务器端:

    MultiTalkServer:
    public class MultiTalkServer{ 静态成员变量,记录当前客户的个数 public static void main(String args[]) { 初始化 新建服务器套接字 while(listening){//监听 监听到客户请求,根据得到的Socket对象和客户计数创建服务线程,并启动它 }
    ServerThread:
    public class ServerThread extends Thread{ 数据成员: 保存与本线程相关的Socket对象/保存本进程的客户计数新建哈希表,用于保存用户的Ip与socket的键值对String IP用于保存IP地址的模板 构造方法 初始化socket变量 初始化clientnum变量将IP和socket放入哈希表中 splitMessage判断用户是否退出当发送空消息或发送“quit”时则退出 用于发送文件 SendDocument 用于创建输入输出流并收发文件 线程主体run() //获取哈希表的大小并输出; //创建输入输出流 while(true) {//监听 ①判断是否要下线如果下线则发送quit并将下线用户套接字移除 ②判断输入的IP是否为正确的IP地址如果IP地址不能和模板相匹配,则输出Wrong IP ③判断目标客户是否在线如果不在线则输出:The Client you call is not online! ④判断是否传输文件 获取目标的Socket套接字 调用模块进行文件发送 关闭Socket输出流 关闭Socket输入流 关闭Socket}
    四、系统测试4.1 收发消息基本功能测试在用户输入对象的IP 发送的消息后,对方可以收到

    4.2 收发文件进本功能测试在用户输入对象的IP +消息+文件路径后,对方可以收到

    4.3 异常测试4.3.1 发消息4.3.1.1 格式错误4.3.1.2 格式正确
    IP地址错误
    目标客户不在线

    4.4 发文件4.4.1 格式错误会当成消息发送出去
    4.4.2 格式正确
    IP地址错误
    目标客户不在线
    找不到文件
    文件重名(文件覆盖)

    会成功发送,但是会将源文件覆盖。
    五、课程设计总结在本次课程设计中,学会了Java网络编程。实现了端到端通信,实现并行通信模式(不再是你说一句,我说一句,一端的信息发送不受另一端的影响),实现端到端的文件传输。学会了收发消息、收发文件的相关操作,输入输出流的包装,知道了做项目的基本流程。理解了Java要按接口编程的核心思想。
    1 评论 0 下载 2019-04-20 23:25:07 下载需要5点积分
  • 基于C#实现的双人对战与道具赛的俄罗斯方块小游戏

    1.系统总体设计要完成本系统中俄罗斯方块,必须考虑以下几个问题:

    如何判断一个方块下落到底部或是发生碰撞
    如何预测一个方块最终能下落到达的位置并给以玩家提示
    如何判断一个方块在进行左右移动或是旋转时不碰触边界与其他方块
    如何让一个方块时时下落更新
    如何将道具俄罗斯方块与普通俄罗斯方块区分

    为此,需要为上面的几个判断依次写明方法,最后可重用,而对于俄罗斯方块的位置可以使用数组存放。
    特别的,对于双人俄罗斯方块,还需要考虑:

    要实现本玩家消除多行时给对面玩家添加相应行数
    要实现本玩家消除特定行时给对面玩家添加捣乱方块

    为此,需要写明2个方法记录自己相应的消除行数以及添加给对面玩家。
    数据结构:数组,全局变量。
    2.系统功能设计本程序的主要功能如下图所示:


    运行程序进入主界面,选择单人或双人游戏
    单人游戏

    用方向键来移动,上方向键来旋转下落过程中方块会实时产生下落预测图方块碰到左右边界无法继续移动,方块旋转后产生的图形超过边界或碰到已有方块则不能旋转,方块下落到边界或碰到已有方块则不再下落并随机生成新方块新方块有一定几率是特殊方块,红色方块能令所在行随机消除,绿色则是随机填充当方块产生的初始位置被填充,游戏结束
    双人游戏

    玩家一用方向键移动,上方向键旋转,玩家二用wasd移动,w键旋转2内bcde当玩家连续消除2行或3行方块时,给对手增加这些方块当玩家连续消除4行方块时,给对方添加特殊方块,红色方块能令所在行随机消除,绿色则是随机填充
    游戏结束

    3.类的设计


    类名
    Form1




    属性
    int x1 玩家一方块一的x坐标


    属性
    int y1 玩家一方块一的y坐标


    属性
    int x2 玩家一方块二的x坐标


    属性
    int y2 玩家一方块二的y坐标


    属性
    int x3 玩家一方块三的x坐标


    属性
    int y3 玩家一方块三的y坐标


    属性
    int x4 玩家一方块四的x坐标


    属性
    int y4 玩家一方块四的y坐标


    属性
    int x5 玩家二方块一的x坐标


    属性
    int y5 玩家二方块一的y坐标


    属性
    int x6 玩家二方块二的x坐标


    属性
    int y6 玩家二方块二的y坐标


    属性
    int x7 玩家二方块三的x坐标


    属性
    int y7 玩家二方块三的y坐标


    属性
    int x8 玩家二方块四的x坐标


    属性
    int y8 玩家二方块四的y坐标


    属性
    int size 方块的长宽


    属性
    int youtu[,] 记录玩家一各个区域有无方块


    属性
    int P2youtu[,] 记录玩家二各个区域有无方块


    属性
    int type 记录玩家一方块代号


    属性
    int P2type 记录玩家二方块代号


    属性
    int turn 记录玩家一方块旋转次数


    属性
    int P2turn 记录玩家二方块旋转次数


    属性
    int specialtype 记录玩家一特殊方块代号


    属性
    int P2specialtype 记录玩家二特殊方块代号


    属性
    bool onlyone 记录是否是单人模式


    方法
    private bool meetnow(int tempx1,int tempx2,int tempx3,int tempx4, int tempy1, int tempy2,int tempy3,int tempy4,int[,] temp) //旋转后是否碰撞的计算函数


    方法
    private void special_init(ref int tempspecialtype)//特殊方块生成


    方法
    private void block_init(ref int type,ref int turn)//玩家一普通方块生成


    方法
    private void P2block_init(ref int P2type, ref int P2turn) //玩家二普通方块生成


    方法
    private void tianblock_init(ref int x1,ref int x2,ref int y1,ref int y2,ref int x3,ref int x4,ref int y3,ref int y4)//田子型block初始化


    方法
    private void long_init(ref int x1, ref int x2, ref int y1, ref int y2,ref int x3,ref int x4,ref int y3,ref int y4)//长条型block,type1


    方法
    private void turn0_long_init(ref int tempx1, ref int tempx2, ref int tempy1, ref int tempy2, ref int tempx3, ref int tempx4, ref int tempy3, ref int tempy4,ref int tempturn)//长条型block旋转


    方法
    private void turn1_long_init(ref int tempx1, ref int tempx2, ref int tempy1, ref int tempy2, ref int tempx3, ref int tempx4, ref int tempy3, ref int tempy4, ref int tempturn)//长条型block旋转2


    方法
    private void tublock_init(ref int x1, ref int x2, ref int y1, ref int y2, ref int x3, ref int x4, ref int y3, ref int y4)//凸型block初始化


    方法
    private void turn0_tublock_init(ref int tempx1, ref int tempx2, ref int tempy1, ref int tempy2, ref int tempx3, ref int tempx4, ref int tempy3, ref int tempy4,ref int tempturn)//凸型block旋转


    方法
    private void turn1_tublock_init(ref int tempx1, ref int tempx2, ref int tempy1, ref int tempy2, ref int tempx3, ref int tempx4, ref int tempy3, ref int tempy4, ref int tempturn)//凸型block旋转2


    方法
    private void turn2_tublock_init(ref int tempx1, ref int tempx2, ref int tempy1, ref int tempy2, ref int tempx3, ref int tempx4, ref int tempy3, ref int tempy4, ref int tempturn)//凸型block旋转3


    方法
    private void turn3_tublock_init(ref int tempx1, ref int tempx2, ref int tempy1, ref int tempy2, ref int tempx3, ref int tempx4, ref int tempy3, ref int tempy4, ref int tempturn)//凸型block旋转4


    方法
    private void RZblock_init(ref int x1, ref int x2, ref int y1, ref int y2, ref int x3, ref int x4, ref int y3, ref int y4)//RZ型block


    方法
    private void turn0_RZblock_init(ref int tempx1, ref int tempx2, ref int tempy1, ref int tempy2, ref int tempx3, ref int tempx4, ref int tempy3, ref int tempy4,ref int tempturn)//RZ型block旋转


    方法
    private void turn1_RZblock_init(ref int tempx1, ref int tempx2, ref int tempy1, ref int tempy2, ref int tempx3, ref int tempx4, ref int tempy3, ref int tempy4,ref int tempturn) //RZ型block旋转2


    方法
    private void LZblock_init(ref int x1, ref int x2, ref int y1, ref int y2, ref int x3, ref int x4, ref int y3, ref int y4)//LZ型block


    方法
    private void turn0_LZblock_init(ref int tempx1, ref int tempx2, ref int tempy1, ref int tempy2, ref int tempx3, ref int tempx4, ref int tempy3, ref int tempy4,ref int tempturn) //LZ型block旋转


    方法
    private void turn1_LZblock_init(ref int tempx1, ref int tempx2, ref int tempy1, ref int tempy2, ref int tempx3, ref int tempx4, ref int tempy3, ref int tempy4,ref int tempturn)// LZ型block旋转2


    方法
    private void LLblock_init(ref int x1, ref int x2, ref int y1, ref int y2, ref int x3, ref int x4, ref int y3, ref int y4)//LL型block


    方法
    private void turn0_LLblock_init(ref int tempx1, ref int tempx2, ref int tempy1, ref int tempy2, ref int tempx3, ref int tempx4, ref int tempy3, ref int tempy4,ref int tempturn) //LL型block旋转


    方法
    private void turn1_LLblock_init(ref int tempx1, ref int tempx2, ref int tempy1, ref int tempy2, ref int tempx3, ref int tempx4, ref int tempy3, ref int tempy4,ref int tempturn) //LL型block旋转1


    方法
    private void turn2_LLblock_init(ref int tempx1, ref int tempx2, ref int tempy1, ref int tempy2, ref int tempx3, ref int tempx4, ref int tempy3, ref int tempy4,ref int tempturn) //LL型block旋转2


    方法
    private void turn3_LLblock_init(ref int tempx1, ref int tempx2, ref int tempy1, ref int tempy2, ref int tempx3, ref int tempx4, ref int tempy3, ref int tempy4,ref int tempturn) //LL型block旋转3


    方法
    private void RLblock_init(ref int x1, ref int x2, ref int y1, ref int y2, ref int x3, ref int x4, ref int y3, ref int y4)//RL型block,


    方法
    private void turn0_RLblock_init(ref int tempx1, ref int tempx2, ref int tempy1, ref int tempy2, ref int tempx3, ref int tempx4, ref int tempy3, ref int tempy4,ref int tempturn)


    方法
    private void turn1_RLblock_init(ref int tempx1, ref int tempx2, ref int tempy1, ref int tempy2, ref int tempx3, ref int tempx4, ref int tempy3, ref int tempy4,ref int tempturn)


    方法
    private void turn2_RLblock_init(ref int tempx1, ref int tempx2, ref int tempy1, ref int tempy2, ref int tempx3, ref int tempx4, ref int tempy3, ref int tempy4,ref int tempturn)


    方法
    private void turn3_RLblock_init(ref int tempx1, ref int tempx2, ref int tempy1, ref int tempy2, ref int tempx3, ref int tempx4, ref int tempy3, ref int tempy4,ref int tempturn)


    方法
    private void button1_Click(object sender, EventArgs e)//单人游戏开始


    方法
    private void button2_Click(object sender, EventArgs e) 双人游戏


    方法
    private void timer2_Tick(object sender, EventArgs e) 双人游戏页面实时刷新


    方法
    private void timer1_Tick(object sender, EventArgs e) 单人游戏页面实时刷新


    方法
    protected override bool ProcessCmdKey(ref Message msg, Keys keyData) 键盘监听方法重写



    主要成员方法的流程图
    private bool meetnow(int tempx1,int tempx2,int tempx3,int tempx4, int tempy1, int tempy2,int tempy3,int tempy4,int[,] temp) //旋转后是否碰撞的计算函数

    private void block_init(ref int type,ref int turn)

    private void button1_Click(object sender, EventArgs e)//单人游戏开始

    4.主程序设计
    定义了变量int x1-x8,y1-y8 记录方块坐标
    定义了变量int size 记录方块大小
    定义了变量两个数组int youtu[,],int P2youtu[,] 存放已经下落的方块
    定义了变量 int type P2type 记录玩家方块代号
    定义了变量int turn int P2turn 记录玩家方块旋转次数
    定义了变量int specialtype int P2specialtype 记录玩家特殊方块代号
    定义了变量bool onlyone 记录是否是单人模式
    主要的功能实现是在Form1.cs中实现的

    在 类 Form1中

    根据游戏区的长宽对方块大小初始化中
    实时刷新页面下落方块以及生成下落预测页面,并检测当前方块是否发生碰撞,如果发生碰撞,则调用block_init重新生成新方块,如果最上方已经有方块,则游戏结束
    通过方法ProcessCmdKey(ref Message msg, Keys keyData)与meetnow,实现玩家用方向键或wasd键改变方块坐标并检测该坐标是否有方块,以此实现方块的左右移动,加速下落,旋转方块

    5.调试分析
    画图闪烁
    解决方式:不在控件上直接绘图,使用双缓冲,先构造新的位图,在其上绘图,然后将picturebox的图片设置为该图,而不是在picturebox上绘图。
    如何让方块保留在图上同时有让新的方块下落
    解决方法:构造两个新的位图,一个为背景图,一个为前景图。
    键盘响应事件对方向键无反应
    解决方法:重写键盘响应事件。
    如果一开始就按下方向键会报错
    解决方法:如果一开始就按下方向键会报错,因为是将坐标转换为数组的10*20行这样y1/size的坐标就是负的, 数组没有负的,故在该方法执行前判断是否y大于0。
    如果向左或向右突然插空到已有方块上方,图形会向下一格再绘图,而不是like检测到碰撞
    解决方法:更改原先绘图算法,原先算法有问题,在实时刷新时是先向下下落再检测是否会碰撞,未考虑键盘事件的影响,故将算法改为实时刷新时先检测碰撞再绘制图片。
    左右移动碰撞被阻拦会闪烁
    解决方法:键盘按下时是单独处理的 这时候也应该进行绘图。
    图片旋转后总是下降一行
    解决方法:键盘按下时是单独处理的 刷新写在计时器,应该旋转也写一个。
    双人在给对方增加行的时候下面有方块的全部往上挪,也即如果该列有方块,会填满到该列最上层
    解决方法:原先算法有问题,在给对方增加行的时候 不止上一行等于当前这行,当前这行也得置
    特殊方块中的生长方块上线行生长几乎一样,消除方块消除也总是一样,给对面增加行的时候留空也几乎一样
    解决方法:C#random是伪随机,短时间内生成的随机数几乎一样,故使用guid做随机数种子真随机
    在最后一行旋转时报超出数组边界
    解决方法:最后一行旋转产生的图片可能会超过边界,这时候要先判断是否到底

    6.测试结果启动程序

    下落方块

    特殊方块1

    特殊方块1效果

    特殊方块2

    特殊方块2效果
    1 评论 2 下载 2019-04-20 16:16:43 下载需要14点积分
  • 基于JAVA的葫芦娃小游戏


    使用javaFx作为框架编写,并根据MVC框架将视图和控制分离
    双击SimpleHuluGameFx.jar运行游戏_
    或进入proj文件夹使用命令mvn package执行测试并生成jar可执行文件,java -jar target\hulagame-1.0-SNAPSHOT.jar运行游戏
    120181227.xml为一次游戏的存档,正确读档后会重现妖怪的一次胜利过程(剩下最后一击由您完成)

    1. 使用方法1.1 运行游戏通过双击ljy.jar运行游戏,准备界面
    点击“开始游戏”开始一个新游戏,点击“读取文件”读取一个存档,点击右上角的”X“退出游戏。所有窗口都可以拖动调整位置
    1.2 新游戏开始一个新游戏后会首先进入模式选择界面,共有“控制人类战斗”,“控制腰妖怪战斗”,“自动战斗”,““双人模式”四种游戏模式。

    控制人类战斗:点击左侧的葫芦兄弟头像即可开始协助人类战斗。
    控制妖怪战斗:点击右侧的蝎子精头像即可开始协助妖怪战斗。
    自动战斗:点击自动战斗后人类和妖怪将由电脑控制开始战斗。
    双人模式:两名玩家分别控制人类和妖怪互相进攻。

    开始游戏后会有一段入场动画,等到所有人物入场完毕后即可开始游戏。

    1.2.1 移动使用鼠标选中一个角色后再点击空白区域即可将该角色移动到目的地。无法移动到有角色的位置
    1.2.2 攻击选中一个角色后左侧会弹出技能栏,由上到下有三个按钮分别为普通攻击、对群攻击和必杀技攻击。不同角色和攻击种类的攻击效果不同。

    普通攻击: 普通攻击可以使用无限次,没有远程属性的角色会攻击面前的敌人,具有远程属性的角色可以攻击在同一行上远距离的敌人。
    对群攻击:对群攻击会消耗一定的MP,会对前、上、下三个位置的敌人造成伤害。
    必杀技攻击:必杀技攻击每个角色每回合只能使用一次,不消耗MP,具有极高的伤害,攻击范围同普通攻击。

    无法使用的攻击方式不会在属性栏中再出现
    1.2.3 治疗某些角色为辅助角色,只能够为己方治疗,不能够攻击敌人。人类方为具有远程治疗能力的爷爷,妖怪方面为具有近程治疗能力的蛇精,使用方法和攻击一致。
    1.2.4 人物状态鼠标移动到角色上时下方会弹出角色的属性栏,包括角色的HP和MP。属性框会在无操作两秒后隐藏。

    以后可能会加入攻击力等更详尽的信息。

    1.2.5 死亡角色HP耗尽后会死亡并变成墓碑,退出战斗。
    1.2.6 人类人类阵营包括七个葫芦娃和爷爷。

    爷爷:
    爷爷是人类阵营的辅助角色,可以远程治疗其他人类。
    葫芦兄弟:
    大娃HP最多(120),并且必杀技攻击力极强(90),不过普通攻击(10)和对群攻击(20)较弱。二娃为远程攻击角色,并且对群攻击可以释放出闪电,威力极强(40)。三娃为远程攻击角色,普通攻击很弱(5),不过必杀技很强(80),可以在后方协助近战角色实现斩首战术。四娃负责主要攻击输出,生命值很低(60),不过对群攻击(40)和必杀技(90)都极高,在战场上经常需要爷爷的治疗。五娃为综合能力最强的角色,三种攻击力分别为(20,30,90),且血量为(100)。六娃血量(150)和普通攻击(30)都很高,适合冲锋在最前方。七娃是最弱的角色。

    1.2.7 妖怪妖怪分为如下三种,分别具有不同特性:

    蝎子精:
    是小妖怪的首领,妖怪阵营的首领,具有极强的攻击力和血量,如果将其击溃则小妖精不会再变换阵型。蝎子精使用普通攻击\必杀技时其他小喽啰也会一起普通攻击\必杀技蝎子精使用对群攻击时其他小喽啰会变阵Tips: 蝎子精在第二回合后可以使用群攻型必杀技,即带领所有小妖怪使用必杀技。Tips: 注意!蝎子精胆子很小如果受到大量伤害会变阵退到阵型的最后方,因此最好抓住机会在一个回合内一击必杀。
    小喽啰:
    最基础的小妖怪,不具有远程攻击能力,攻击力和HP值都很低,不过数量众多。
    蛇精:
    辅助型妖怪,具有比爷爷还要强大的对群治疗能力,会在每回合对妖怪进行一次治疗。Tips: 为了避免被小妖怪消耗致死,需要尽快击破蛇精后减少小妖怪的数量。


    以后可能会增加双人对战模式。

    1.2.8 回合本游戏为回合制游戏,为平衡人类和妖怪的实力,每个回合协助人类时具有三次有效操作机会(移动,攻击,治疗),协助妖怪时有两次有效。
    使用妖怪阵营时操纵小妖怪布阵不消耗操作机会

    人类剩余操作数会以红色圆球的方式在窗口上方显示:


    妖怪剩余操作数会以红色圆球的方式在窗口上方显示:

    玩家操作用尽后会进入敌人的攻击回合,包含如下三个部分:

    妖怪的自动攻击(玩家选择人类阵营):
    所有攻击型妖怪具有一次任意方式攻击机会蛇精会寻找受伤的妖怪治疗蝎子精可以选择是否变阵
    人类的自动攻击(玩家选择妖怪阵营):
    所有葫芦娃移动一次到合适的攻击地点所有葫芦娃可以进行一次攻击,近战葫芦娃会优先使用必杀技,远程葫芦娃会将必杀技留给蝎子精爷爷会寻找受伤的葫芦娃治疗

    下面是操纵人类战斗两个回合的演示:

    1.2.9 胜负判定当一方阵营全部被消灭后游戏结束,弹出结束提示,游戏结束后可以继续操作人物,不过系统不再保存人物行为。
    1.2.10 自动战斗自动战斗模式下人类和妖怪会自主按照自己的战斗逻辑战斗,此时无法控制角色,只能查看角色状态。
    下面是一小局自动战斗的演示:

    1.3 保存游戏随时可以保存游戏,点击游戏界面右上角的保存图标

    即可保存当前进度,游戏进度会保存为xml文件。
    1.4 读取进度在准备界面点击“读取文件”后选择一个之前保存的文件即可开始读取。目前的实现的自动播放一遍之前的所有游戏过程后,此阶段无法控制人物。在加载完毕后即可继续游戏。
    为了增强游戏体验,读取进度时角色移动动画和攻击动画都会加倍播放。
    1.5 退出游戏在准备界面和游戏界面右上角都有关闭按钮

    点击即可关闭游戏。
    2. 设计思路此版本和之前版本设计思路完全不同,采用了MVC设计模式,javaFx开发框架。下面从Model, View和Control三个方面简述设计思路。
    2.1 资源管理为了方便资源管理,将程序需要的所有属性和资源都封装到抽象类Configs和ViewBundle中。
    Configs中保存窗口布局相关的数值属性和游戏中人物贴图和其他游戏中使用的系统贴图,所有贴图通过ArrayList储存,并提供static final int下标按照如下命名格式进行索引。
    //贴图索引 public static final int INDEX_PREBACKGROUND = 0; public static final int INDEX_BACKGROUND = 1; public static final int INDEX_START = 2; public static final int INDEX_LOAD = 3; public static final int INDEX_CLOSE = 4; public static final int INDEX_DCLOSE = 5; //... //布局属性 public static int WIN_HEIGHT; public static int WIN_WIDTH; public static int TOP_MARAGIN; public static int BOTTOM_MARAGIN; public static int LEFT_MARGIN; public static int RIGHT_MARGIN; //...
    ViewBundle保存所有角色的攻击效果贴图,同样按照index寻址
    3. MVC模式说明3.1 模型Model本游戏主要模型即角色(Charactor)。
    3.1.1 角色每个角色都运行在一个线程中,具有攻击力、HP、MP等战斗属性和当前位置、移动、存活等其他属性。Charactor的主要逻辑如下:
    /** 角色在真实画布上的位置*/ public AtomicInteger PositionX=new AtomicInteger(0), PositionY=new AtomicInteger(0); /** 当前是否正在执行操作*/ public AtomicBoolean avaliable=new AtomicBoolean(true); /** 用于接收命令*/ public AtomicInteger cmd=new AtomicInteger(0); @Override public void run() { while (true) { try { Thread.sleep(50); } catch (Exception e) { e.printStackTrace(); } cmdHandler(); //命令处理函数 } } /** 在循环中等待新命令*/ protected void cmdHandler() { switch (cmd.get()) { case 0: break; //当前无任务 case 1: WalkToDst(); cmd.set(0);break; //1 号命令为移动到目的地 case 2: Attack1(); cmd.set(0); break; //2 号命令为普通攻击 case 3: Attack2(); cmd.set(0); break; //3 号命令为对群攻击 case 4: Attack3(); cmd.set(0); break; //4 号命令为必杀攻击 default: break; } }
    移动:角色移动过程中会在线程中定时更新自己的坐标和当前贴图的描述,OutlookManager定期使用对应的的贴图重绘即可形成移动的动画。
    攻击:每个攻击效果(Bullet)对象具有自己的位置和贴图属性,Charactor通过一个静态类bulletController获得攻击效果移动的逻辑后更新Bullet的位置,OutlookManager定期将攻击效果绘制在Canvas上形成攻击动画。
    3.2 视图View在javaFx中我使用的主要视图控件为没有布局的Pane,游戏界面的主要控件如下:

    场景Stage:
    场景中的Pane负责承载所有其他UI对象,并接收鼠标行为,并将MouseEvent传递给battleManager做出解析。
    画布Canvas:
    画布用来绘制所有战斗动画效果并形成一个FPS动画,由视图控制类OutlookManager控制每秒更新36次,形成连贯动画。
    技能栏SelectionBar:
    技能栏负责显示当前人物可用的技能,接收使用技能的命令,并传递给战斗控制类battleManager处理。当前无选择角色时会隐藏,重新选中角色后battleManager会通知技能栏更新数据并弹出。
    属性栏CInfoBar:
    属性栏负责显示当前鼠标经过的角色当前属性,内置一个计时器,会在无操作两秒后隐藏,battleManager在需要出现时通知属性栏弹出。

    3.3 控制Control游戏中主要的控制类有三个:控制战斗逻辑的battleManager,负责刷新界面的OutlookManager,控制人类行为的hBot类和负责控制妖怪行为的mBot类,其中后三者都为前者的成员变量。所有控制类都有一个角色数组储存所有角色的引用。

    battleManager
    battleManager会创建所有角色并开始其线程,其主要逻辑如下:

    static final int ACTION_CLICKED = 0; static final int ACTION_MOVEABOVE = 1; /** 接收一个鼠标事件*/ public Charactor newAction(iPoint loc, int type) { //重放时不能控制角色 if(type == ACTION_CLICKED && autoplaying) return null; for(Charactor x: creatures) { if(x.alive && x.avaliable.get()) { iPoint vp = virtualField.rpTovp(x.PositionX.get()+Configs.B_SIZE/2, x.PositionY.get()+Configs.B_SIZE/2); if(loc.x==vp.x && loc.y==vp.y) { if(type == ACTION_CLICKED) //如果是点击则为选择一个人类角色 ChatSelected = x; if(type == ACTION_MOVEABOVE || (!bind.get() && !x.monster)) //如果为鼠标经过则为显示角色属性 return x; } } } if(type == ACTION_CLICKED) { if(ChatSelected!=null && ChatSelected.monster) ChatSelected = null; //TODO: 通知当前选中角色移动,保存玩家操作并减少玩家步数 if(!bind.get() && ChatSelected != null) { ... } } return null; } /** 玩家步数减少1,并且判断是否轮到敌人进攻*/ public void stepDecrease() { if(End) return; stepRemain--; //步数减一 view.hint.set(stepRemain); if(stepRemain==0) //轮到妖怪回合 { bind.set(true); //禁用玩家操作 new Thread(()->{ while(true) //等待所有角色结束当前行为 { ... } bot.nextMove(); //阻塞等待怪物完成自己的回合 bind.set(false); //启用玩家操作 }).start(); stepRemain = 3; view.hint.set(stepRemain); } } /** 游戏结束,通知View执行结束动画并终止战斗逻辑*/ public void GameEnd(boolean monster) { ... }

    OutlookManager

    OutlookManager本身为一个线程,每1/36s会重绘一次Canvas制作逐帧动画,需要注意的是所有与视图相关操作都需要在主线程中完成,这里在重绘时使用了Platform.runlater避免出现同步问题。
    mBot和hBot

    Bot类封装的是电脑控制的战斗逻辑,提供public void nextMove()接口,计算、通知、并阻塞等待所有妖怪\人类完成自己的行为。

    4. 其他重要组件4.1 游戏保存SaveStack负责保存游戏进度,提供如下接口:
    /** * 添加一次操作信息 * @param chatId 角色ID * @param dstx 移动目的地横坐标 * @param dsty 移动目的地纵坐标 * @param type 操作类型 */ public void addMove(int chatId, int dstx, int dsty, int type)
    每次玩家做出有效操作后battleManager或SelectionBar会调用此接口保存玩家操作,点击保存按钮后会将当前xml树写入文件。保存时采用xml文件,格式如下:
    <?xml version="1.0" encoding="UTF-8" standalone="no"?><battle> <ctime>2018-12-19 02:38:22</ctime> <battle> <move ChatId="3" X="2" Y="3" fmt="0">0</move> <move ChatId="3" X="0" Y="0" fmt="0">4</move> <move ChatId="3" X="0" Y="0" fmt="0">2</move> <move ChatId="8" X="0" Y="0" fmt="0">5</move> <move ChatId="9" X="9" Y="0" fmt="0">0</move> </battle></battle>
    4.2 读取进度AutoPlayer负责解析xml文件并在线程中控制每个角色的行为,battleManager会一直设置禁用玩家操作直到读取结束(此线程结束)。
    4.3 线程同步问题
    所有只涉及同步读写的简单同步问题(如角色的位置)都使用Atomic变量保证同步。
    所有视图相关操作都在主线程中完成。
    属性栏和技能栏的自动弹出/消失存在较复杂的同步问题,如在弹出过程中接收到消失指令。这里使用两个Lock锁和一个boolean变量hide保证同步具体逻辑见CinfoBar.java或SelectionBar.java中的Hide和Showup方法。
    发现在变阵时会出现如下问题:

    例:A,B线程在数组中移动,其中A: 1->2、B: 3->1 <br>    B: clear 3 <br>    B: write 1 <br>    A: clear 1 <br>    A: write 2 <br>如按照上述顺序执行则B会消失。

    已经通过移动后重新刷新cmap修复。

    4.4 战场透视映射由于之前视图时通过虚拟坐标映射、并且将视图和控制分离,因此实际上不需要对模型进行修改,这也是MVC模式的方便之处。
    只需在OutLookManager绘制FPS每一帧时进行透视坐标转换即可,透视算法在知道四个基准点的映射前后位置实际上可以看作线性插值。之前方格的布局是通过Configs.B_SIZE设定的,为了完成透视映射为Configs增加如下方法:
    /** 判断坐标(x,y)是否在row, col透视后的四边形中。* * @return 如果在四边形中则返回此四边形的属性Block, 否则返回null */ public static Block IsInBlock(double x, double y, int row, int col) /** * 获得一个透视后方格的属性 * @param x 透视前纵坐标 * @param y 透视前横坐标 * @return Block对象,描述方格的属性 */ public static Block SPEC_MID_SIZE(int x, int y)
    其中Block(Configs.java)中包含了透视映射后的梯形位置信息。
    4.5 注解在Annotations.java文件中定义了注解类型IsCharactor和用于解析注解并生成说明文档的HintGenerater类。
    4.5.1 IsCharactorIsCharactor包含一个人物的姓名、战斗属性等基本属性,对所有继承自Charactor类的角色都添加此注解。
    4.5.2 HintGeneraterHintGenerater使用了单例模式,每次运行游戏只会生成一个文档,并且生成完毕后立刻销毁HintGenerater对象,主要代码如下:
    class HintGenerater{ static HintGenerater generater; static Class[] clazzlist = {Grandpa.class, Scorpion.class, Snake.class}; static String helpstring = ""; static{ generater = new HintGenerater(); } private HintGenerater(){ } //隐藏初始化器 public static boolean GenerateHintDoc(){ if(generater==null) return false; CucurbitBoys[] brothers = CucurbitBoys.values(); for(CucurbitBoys boy:brothers) { ...//由于葫芦娃是通过enum类生成的,因此无法需要单独解析 } for(Class clazz:clazzlist) { if(clazz.isAnnotationPresent(IsCharactor.class)) { ...//解析注解内容 } } //保存到文件Hint.txt中 generater = null; //生成后即销毁HintGenerater return true; }}
    4.6单元测试使用JUnit实现了两个测试,检测是否缺失资源文件和检测UI布局属性是否正确、检测虚拟坐标和真实坐标相互转换算法是否正确。
    需要注意使用JavaFx直接做测试时会抛出Internal graphics not initialized yet异常,我直接为AppTest类增加了一个JFXPanel初始化Graphic。
    测试代码如下:
    @Test public void testConfigs() { try{ Configs.initialize(); }catch (IllegalArgumentException e){ assertTrue("贴图加载失败,请重新下载贴图",false); //如果缺少贴图则在加载是会抛出无效URL的异常 }catch (Exception e){ assertTrue("未知加载错误",false); //其他异常 } //MARK: 左侧边界+右侧边界+战斗区域 = 窗口宽度 assertTrue("横向布局参数错误",Configs.LEFT_MARGIN + Configs.B_SIZE*Configs.B_WNUM + Configs.RIGHT_MARGIN == Configs.WIN_WIDTH); //MARK: 上方边界+上方边界+战斗区域 = 窗口高度 assertTrue("纵向布局参数错误",Configs.TOP_MARAGIN + Configs.B_SIZE*Configs.B_HNUM + Configs.BOTTOM_MARAGIN == Configs.WIN_HEIGHT); assertTrue( true ); } @Test public void testVpToRp() { Configs.initialize(); Random random = new Random(); //MARK:100次随机坐标转换测试, 测试坐标应该落在同一个格子中,即返回的虚拟坐标为(3,2) iPoint result; for(int i=0;i<100;i++) { //相当于随机在B_SIZE*B_SIZE的方格内选点后判断在哪个格子内 result = virtualField.rpTovp(Configs.LEFT_MARGIN + 3 * Configs.B_SIZE + random.nextInt(Configs.B_SIZE-1), Configs.TOP_MARAGIN + 2 * Configs.B_SIZE + random.nextInt(Configs.B_SIZE-1)); assertTrue( "实际坐标到虚拟坐标转换错误",result.x == 3 && result.y == 2); } result = virtualField.vpTorp(0,0); //测试(0,0)虚拟方格的起始坐标应该恰好为上方边界宽度和左侧边界宽度。 assertTrue( "虚拟坐标到实坐标转换错误",result.x == Configs.LEFT_MARGIN && result.y == Configs.TOP_MARAGIN); assertTrue(true); }
    1 评论 0 下载 2019-04-19 19:53:14 下载需要6点积分
  • 基于中央定位服务器的P2P网络聊天系统设计

    1 需求分析即时通讯 (Instant Messaging) 是目前 Internet 上最为流行的通讯方式,各种各样的即时通讯软件也层出不穷;服务提供商也提供了越来越丰富的通讯服务功能。不容置疑,Internet 已经成为真正的信息高速公路。
    微信 (Wechat) 是腾讯公司于 2011 年 1 月 21 日推出的一个为智能终端提供即时通讯服务的免费应用程序。截止到 2016 年第二季度,微信已覆盖中国 94% 以上的智能手机,并由其界面简练的特性使得多数人放弃使用 QQ 转而使用微信。
    本项目要求实现基于中央定位服务器的 P2P 网络聊天系统设计。客户端与服务器之间使用 C-S通信,客户端之间对话使用 P2P 进行通信,这两点即为本项目需要实现的核心。
    2 总体设计2.1 整体流程图
    2.2 状态机
    2.3 选做内容本项目完成了所有的必做任务,选做内容如下。
    动态表情发送 本项目实现了对动态表情发送的功能,支持的文件格式为 gif。
    聊天记录查询 使用 SQL 数据库对聊天记录进行存储。在每次登出时,将所有聊天记录存入数据库;在每次登录成功后,读取数据库中的聊天记录,以实现在登出后再次登录时,能够查询之前的聊天记录。
    快速查找好友并发起对话 一般来讲,每个人会有数百个好友。如果想与某一位好友对话时,如果一点一点翻联系人列表会耗费大量时间,故设计快速查找好友功能以实现对话的快速发起。
    新消息提醒功能 在收到消息时,在列表的正下方会进行消息提醒,即提醒发来新消息的人的学号。
    2.4 开发环境本项目基于 VS2017 及 Blend for VS2017,使用 C# 的 WPF 框架进行开发。其中逻辑编写使用 VS2017,界面的开发基于 Blend。
    3 详细设计该系统主要分为三个模块:逻辑层、界面层以及数据层。三个层次彼此分明:逻辑层和数据层通过初始化时载入数据库以及退出程序时数据保存至数据库的方式对接;逻辑层和界面层通过事件对接。
    3.1 界面层界面层通过对.xaml 文件的编写进行设计。其中,界面风格采用了 material 风格。所设计的聊天界面如下。其中按键分别为添加好友、添加群聊1、好友查找跳转、动态表情发送、文件传输、语音发送2、消息列表3。上方学号旁的绿灯表示在线,若不在线则为灰色。

    3.2 逻辑层3.2.1 账号登录上线及下线账号登录上线即通过对服务器发送指令,账号密码均正确后即登录成功。与服务器之间的交互使用了 C# 中的 System.Net 以及 System.Net.Socket 库4。下线操作即在程序完全退出前,对服务器发送相关指令,基本操作与登录上线相似。
    与服务器的交互过程为,客户端向服务器发起连接,连接后再发送相关的指令。
    3.2.2 通讯录 (查询好友是否在线)查询好友是否在线即向服务器询问所要查询的好友的在线状态及其 IP 地址。若好友在线,则好友指示灯亮起;若好友不在线或者用户不存在以及输入格式出现错误,会通过 MessageBox 进行提醒。
    3.2.3 P2P 通信P2P 通信分为发送部分和接收部分。下面分别叙述本项目的传输协议、P2P 发送以及 P2P 侦听部分。
    协议部分 本项目的协议共 14 位字节,例:20160114980000。其中,前 10 位为学号,第 11 位和第 12 位为类别,第 13 位和第 14 位为扩展位。
    P2P 发送部分 P2P 发送部分与和服务器之间的交互的发送部分几乎相同,即先与 Peer 进行连接,再进行发送。发送前要对相关的信息转换为与协议相关的格式。
    P2P 侦听部分 P2P 接收部分单独开了两个线程进行接收。在其中一个线程下不断循环进行侦听,另一个线程监测是否有新消息提醒,若有新消息提醒,则使用 Invoke 对界面进行更新,以防止程序出现崩溃。侦听在聊天界面的初始化时开始,在完全退出程序前停止侦听。停止侦听采用软停止,即对自己发送 1 个字节的 0,当侦听到时,退出循环即停止侦听。
    3.2.4 文件传输协议说明 文件传输的应用层协议为,在 14 位通信协议的基础上,先是用一个字节表示文件名字的长度,再用若干字节表示文件名字,剩下的为文件的具体内容。
    文件传输 文件传输功能能够传输的最大文件限制在 1G 左右,这一点仿照微信对文件大小限制在100M。除此之外,接收文件时需要手动保存的路径,若放弃选择路径则表示拒绝接收文件。
    3.2.5 聊天记录查询内存记录 为了在程序中记录消息记录,定义了两个聊天记录类,即全部聊天记录类和单条聊天记录类。具体如下。
    //单条聊天记录纪录public class chatHistorySingle{ public string Speaker; //谁说的话 public int chatid; public string Mess; public string Date = "2019/1/1"; public string ModeFile = "0"; public chatHistorySingle(string speaker, int Chatid, string mess,string modefile,string date) { Speaker = speaker; chatid = Chatid; Mess = mess; ModeFile = modefile; Date = date; }}//整个聊天记录public class chatHistory{ public string Monitor; //主机(谁登录的) public string Contact; //给谁发 public List<chatHistorySingle> History = new List<chatHistorySingle>(); public int preLoadHistoryID = 1; public chatHistory(string monitor, string contact) { Monitor = monitor; Contact = contact; } public void AddHistory(chatHistorySingle chs) { History.Add(chs); }}
    其中单条聊天记录类用于记录说话的人、聊天记录、记录 ID、时间、文件类型;整个聊天记录类用于记录登录 ID、联系人 ID 以及众多的单条聊天记录。
    数据库记录 通过 SQL 接口,将聊天记录信息存入数据库。且在每次界面初始化时,将数据读入,以实现在登出后再次登录时,能够再次查询之前的聊天记录。
    3.2.6 动态表情发送动态表情发送与发送文件类似,将收到的动图自动存在当前文件夹中的 Image 文件夹中。通过使用 WPF 的 MediaElement 控件,以及在后端调整其帧使得其能够循环播放显示。
    3.2.7 好友查询及跳转通过使用 C# 的 Lambda 表达式,查询好友列表中是否存在好友且得到其 index,通过与界面层的结合实现跳转功能,便于在数百个好友中迅速跳转到想要对话的好友。
    3.2.8 新消息提醒为了达到即时通讯的目的,需要在接收到消息时了解到消息的发送者是谁,所以需要进行消息提醒操作。使用 SnackbarMessage 控件进行对新消息的提醒,便于操作。在点击 NewMessage 之后,提醒框会自动消失。
    3.2.9 实时显示提示信息在查找好友和添加好友时,实现对提示信息的实时显示,使用户体验更好。
    实时显示提示信息 (添加好友时好友不在线)

    实时显示提示信息 (添加好友时好友格式输入错误)

    实时显示提示信息 (查询好友时,该好友不为你的好友,无法跳转)

    3.3 数据层数据层主要调用了 SQLite 数据库。存储数据分别为登录用户学号、消息发送者学号、联系人学号、聊天消息记录、日期、消息类型、消息的 ID。

    4 结果分析4.1 登录界面登录界面使用了 Card、Texblock、Button、passwordBox 控件。账号密码默认为 2016011498和 net2018。除此之外,隐藏了窗口边缘并自制了最小化和关闭按键,以保证界面的美观,同时拖动窗口的任意位置可以对窗口进行拖动。

    4.2 聊天主界面向中央服务器发出查询,若无误则登录成功进入聊天主界面。
    添加好友 点击左上第一个按键,进行好友添加。添加过程中会有实时提醒输入格式是否正确、好友是否在线、好友是否存在。若好友在线,则添加入通讯录中。

    发送消息点击左侧的通讯录即跳转至与其聊天的界面。在对话框中发送消息,以及发送动图和文件。相关信息会显示在对话框中,且设定滚动条自动追踪最后一条消息。其中文字消息的发送可以点击 Send 键或者直接回车发送。其中,右上的亮灯表示该好友在线。

    查找好友并跳转 点击左上方第三个按键,则弹出好友查询对话框,在输入学号的过程中同样也会做即时提醒。若为好友则直接跳转与其之间的聊天界面。

    发送/接收动态表情 本项目只支持发送并显示动态表情,即电机 StickerButton,然后选择动图并进行发送。

    发送/接收文件 本项目限制最大文件大小为 1G,这一点模仿微信。发送文件即点击文件发送按键,并选择发送的文件。

    接收到文件时会自动弹出保存窗口,选择保存的路径,并且左小角会提醒文件的来源。

    退出程序 若要退出程序,则点击右上角的按键,点击 Quit 再确认即退出。退出后所有数据将存入数据库。

    5 总结与反思由于这学期其他课程众多的大作业以及期末复习的压力,本次计网大作业我在 2018 年底写了整整四天。四天里一直专心的写计网大作业,且这是我第一次使用 WPF 进行界面设计以及第一次使用 C# 进行网络通信编写。本次大作业极大的锻炼了我的快速学习能力以及泛化能力。
    除了对计网课上所讲的相关内容在实践过后有了更近一步的了解,我逐渐感觉到 WPF 的厉害之处。这四天里,一天半界面设计,两天半逻辑编写,以及对 WPF 各种特性的测试,让我越发越感受到其远远超过 Winform 的地方,在编写的过程中让我回想起当初人智作业用 Winform 编写时各种不规范的编程习惯,比如 Winform 的界面更新建议另开线程 (Invoke),否则可能在界面同时刷新时程序会出现崩溃。但是,我了解的 WPF 可能只是冰山一角,比如本次大作业的编写由于时间关系,并没有使用 WPF 的优点之一的 binding 功能。这可能是一种遗憾。
    不过更遗憾的是,我没有去实现群聊和语音功能。一是期末复习压力,时间不够,二是由于群聊的编写可能要稍微修改一下我之前的代码架构,可能会耗费太多的时间。此外,再进行深入思考之后,我感觉或许微信的群聊功能是基于中央服务器的,即可能不是 P2P 的。如果可以的话,其实可以让我们也对服务器部分的代码进行编写,这样更能锻炼我们的应用能力,进而对相关知识有更进一步的了解。
    参考文献[1] https://docs.microsoft.com/en-us/dotnet/index (网络部分)
    [2] https://github.com/MaterialDesignInXAML/MaterialDesignInXamlToolkit (界面部分)
    1 评论 1 下载 2019-04-19 18:15:48 下载需要14点积分
  • 基于Python实现的手写数字识别

    一、准备
    测试准备:pycharm,windows自带画图功能,python3.5
    安装python3.5
    将解压的文件夹拖入pycharm中,设置解释器路径为:文件解压路径\ML_num\venv\Scripts\python.exe
    若要调用文件,只要点入文件并点击右上角绿色三角或鼠标右键点 run 相应文件

    二、手写数字识别调用UI.py 得如图界面:

    测试可用windows自带画图功能,刷子选用喷枪,粗细第3个,尽量喷清楚,图片后缀为.png图片大小为400*400像素(其他大小也行只要不小于32*32)如下:

    点击选择文件找到测试文件,选择是否使用备份(默认使用)再点击开始识别, 可选择自动识别或手动识别,手动识别截图时鼠标左键按住拖动即可(数字周围尽量有些许空,若识别错误,很大原因是由于截的图左右空格太小、太大),再输入真实值,结果为:文件名为输入的真实值_最大数字.txt。以下结果为文件名为:6_3.txt 如图:

    三、摄像头务必使用笔记本电脑,数字尽量写得大一些,尽量在光线明亮处拍摄,点击摄像头,如图:

    按q键退出,其他键拍摄,之后点击开始识别与第二部分相同。
    四、介绍
    使用的非标准库:opencv,numpy,pillow
    机器学习技术:KNN邻近
    引用:《机器学习实战》源码并对其中部分内容修改,增加部分操作;部分代码参考cnblog与CSDN

    文件介绍


    data_set:备份文件夹
    images:图片文件
    testDigits:测试数据
    testDigits(测试):正确率测试数据
    trainingDigits:训练数据
    vedio_cut.py:摄像头拍摄
    hand_number.py:截取图片并修改大小为32*32, 转为灰度图
    zero_one.py:将灰度图转为01矩阵并存入testDigits文件夹内
    kNN.py:k值邻近算法识别数字并统计结果, 可以选择使用备份数据训练识别或重新开始
    get_next_num.py:得到指定数据文件数量
    check:测试正确率
    UI.py:使用tkinter库构造界面
    新增im:样例图片
    1 评论 3 下载 2019-04-19 10:14:01 下载需要7点积分
  • 基于JSP的MVC框架实现的图书推荐系统展示平台网站

    摘 要推荐系统是目前互联网中最常见的一种智能产品形式。由于网络中信息量的快速增长以及图书出版行业出版量的攀升,人们需要一种办法,来解决信息过载的问题。此外,用户访问网络是为了获取信息,但并不是所有的访问都有很强的目的性,所以对于这些没有明确的目的的访问,就需要智能系统把一些用户可能感兴趣的信息推送给用户。基于这些需求,图书网站的推荐功能就变得非常重要。
    本文首先对图书推荐系统的发展历史做了介绍,然后对开发图书推荐系统所需的项目管理工具(Maven、Git)、数据持久化工具(MyBatis,Spring MVC框架)和Bootstrap前端开发框架进行了简单分析,在此基础上,设计并开发了一套基于Web的图书推荐系统展示平台,主要工作可概括为四个方面。

    对图书推荐系统的结构进行了重新设计。主要是在Spring MVC框架的基础上,将系统分为了三层:Web层、服务与模块层、数据层,并对每层的结构与需要完成的功能做了定义。
    基于MySQL数据库管理系统,设计并建立了图书推荐系统所需的数据库,该数据库的数据共分为三个部分:原始数据、清洗后数据和用户数据。
    从便于操作和使用的角度出发,设计了图书推荐系统的页面,主要包括首页、搜索、展示、登录、注册等页面。
    对整个开发过程以及系统组成的三个主要类:控制器类、模块类与视图类进行了分析。

    经过后期的数据库优化与功能测试,系统与同类网站相比,性能良好。
    关键词:基于Web的图书推荐系统;展示平台;MVC框架;Web系统设计
    AbstractRecommender system is one of the most common intelligent productform in the internet recently. Since the growth of the information in thenetwork as well as the growth of the amount of books, people need to solve theproblem of information overload. Meanwhile, the users access to the network toobtain information, but not all have a strong purpose. So the system must pushsome information that user might be interested in to who accesses the websitewithout strong purpose. That is why it is important for website to providerecommendation information.
    First of all, we described the history of book recommender system.Then, made an introductory of the tool and frameworks used in development:Maven, the project management tool, Git, the version control system, MyBatis,the SQL Mapping Framework for Java, Spring MVC framework and Bootstrap, thefront-end framework. Finally, designed and developed a display platform ofweb-based book recommender system on that. The main work is as follows.

    Made redesign of the structureof the book recommendation system. Divided the system into three levels: theweb level, the service and model level and the data level based on the SpringMVC framework. Made the definition of the structure and functions to be done oneach layer.
    Designed and built the databaseof the book recommender system based on the MySQL database management system.Database is divided into three parts: Original data, washed data and user data.
    From the perspective of the easeof operation and use, designed the pages of the book recommender system. Pageshave been divided into index, search, display and login and register parts.
    Made analyses of the wholeprocess of development and the three main classes: controller, model and view.

    After database optimization and functional testing, system performsgood compared with similar sites.
    Key Words:Web-based BookRecommender System;Display Platform;MVC Framework;Web System Design
    引 言图书推荐功能在图书馆网站,图书销售网站,以及图书分享网站中都占有很重要的地位。它可以根据长尾理论挖掘图书数据,改善用户体验,增加用户的粘性,解决信息过载问题。
    本论文的目的就是要构建一个图书推荐系统的展示平台,将图书信息以及推荐内容,显示在页面之上。并且支持用户的注册登录,用来提供数据,使得推荐算法可以依照此数据来计算出用户喜好,进而向用户推送推荐信息。
    在推荐系统领域,最为成功的该属亚马逊网站,在亚马逊的收入中,有20%~30%的都来自于推荐系统。而在图书馆等非盈利性网站,图书推荐系统也对优化用户的浏览体验以及均衡每本书的借阅率,都有促进作用。
    系统后台是基于Java的Spring MVC框架与Tomcat服务器,前台基于浏览器、HTML与jQuery页面展示技术,数据库基于MySQL开源数据库。利用MVC框架,可以方便地构建出健壮、扩展性强的应用。MySQL数据库,作为最热门的开源数据库,提供了完整的关系数据库支持与成熟的数据存储解决方案。浏览器展示技术,使用户不需要在操作系统中安装独立的应用,并且使得系统的访问不局限于某个操作系统,适用面变得更广。系统提供推荐内容的展示,图书排行的展示,图书搜索,图书详细内容展示,用户登录注册等基本的图书推荐系统所具有的功能。界面使用Bootstrap框架,布局对用户更加友好。
    整个系统通过实现一个轻量的框架,来提供完整的图书推荐功能,后续的功能可以方便地在此基础上扩展。相对于其它的系统,有小巧、灵活的优点,并且本系统全部采用开源软件,因此几乎为零成本。在引用第三方库的过程中,不对其源码进行修改,减少耦合,使得之后第三方库的升级也不会对系统有大的影响。系统在一台机器上部署,并且在同一网段的另一台机器上访问测试后,性能表现良好。
    1 文献综述如今,推荐系统无处不在。在网上逛商城,购物,在音乐网站听歌,在社交网站发表自己的感受以及分享照片,网站服务提供商都会收集用户的访问记录以及用户的喜好,对用户的行为进行分析并且根据用户喜好以及用户群体统计信息,给用户提供相似物品的推荐以及广告推送,进而增加商家的销量,提高网站质量。
    一般认为,推荐系统这个研究领域源于协同过滤算法的提出。从它的诞生到现在20多年中,很多学者和公司对推荐系统的发展做出了重要的贡献。随着信息技术和互联网的发展,人们逐渐从信息匮乏的时代走入了信息过载的时代[1]。而图书出版物等作为一个传统的信息载体,在人类发展的过程中不断增多,一些大的互联网公司与网络图书销售商,比如谷歌、亚马逊等,也一直在为图书的数字化做着贡献。从2004年谷歌宣布他的图书搜索服务之后,到2012年,共有两千万本图书扫描、经由光学字符识别并存储于数字化数据库中作为搜索数据[2]。这些数据为图书推荐提供了丰富的基础。
    本系统采用BS结构,对数据库中的图书内容进行查询、处理,对查询结果进行展示,并且提供用户注册登录以及采集用户访问记录的功能。在服务器端采用Spring MVC框架与对象持久化技术对图书信息进行处理,在客户端使用浏览器技术对数据进行展示与交互,最终完成图书通过WEB平台展示、推荐与相关图书搜索的功能。
    1.1 课题背景1.1.1 图书推荐系统发展背景随着互联网的快速发展,网络中的图书信息量迅速增长,图书种类也日趋繁多,用户通过互联网要获得自己感兴趣的图书文章需要花费越来越多的时间。由此催生了图书推荐系统。图书推荐系统的基本作用是依据用户的访问记录,特定行为,分析用户的喜好,主动向用户推荐可能喜欢的图书与文献给用户,供用户参考[3]。推荐系统满足了用户个性化的需求,节省了用户搜寻信息的时间,获得和用户喜好相关的最热门最新的图书。在许多商务网站,社交网站中,都使用推荐系统来向用户推荐商品。
    在线售卖领域,亚马逊拥有最好的图书推荐系统。在过去的十多年里,亚马逊投入了大量的精力去建立一个包含大众推荐和个人记录的高效个人推荐系统,其最显著特点就是所有的推荐都建立在对顾客以往的浏览记录和购买记录之上[4]。
    亚马逊可以向用户精确的投放畅销书以及经典书推荐信息,每月有评有畅销书排行榜,通过简洁美观的介绍与醒目的导航,引导读者阅读与购买。另外,在畅销书的介绍中,还有本书在相关的分类栏目中的排名,并且有链接指向此分类的畅销排行榜,读者可以方便的查询同类书中最畅销的书籍。在书籍介绍底端还有购买过此书的人还购买了的书籍,购买的比例,以及同类书籍的推荐信息。在首页还有促销书的推荐,用于吸引没有明确目的,而想要以优惠的价格购书的消费者。在经典书方面,亚马逊推出年度畅销图书排行,年度编辑推荐100本畅销书等手段,依据惯性定律,进一步推动图书的销售。其中排序规则还根据用户的评价,人气、出版日期等,迎合不同人的喜好。
    1.1.2 主要技术发展背景Spring框架是一个开源的应用框架,基于Java平台的控制反转容器。第一个版本是Rod Johnson在他2002年十月出版的《ExpertOne-on-One J2EE Design and Development》书中发布的。第一次在Apache 2.0 license下发布是在2003年六月,在2004年三月,发布了第一个里程碑:1.0版本。在2006年,Spring框架1.2.6版本获得了Jolt productivity award和JAX Innovation Award。当前的版本3.2.2是在2013年三月发布的。
    Spring Framework的核心特性可以被用于任何的Java应用。尽管SpringFramework没有强加任何特殊的编程模块,它依旧在Java社区变得流行,并且有取代EnterpriseJavaBean模块的趋势。
    在2001年,Clinton Begin开始一个叫做iBATIS的项目,刚开始,开发的重点是一个加密的软件解决方案。iBATIS项目发布的第一个软件名字叫做Secrets,一个个人的数据加密和登录工具。Secrets完全的用Java实现,并且在开源协议下发布。
    2002年,Clinton开发了一个应用叫做JPetStore来展示Java可以比.NET更高效,JPetStore 1.0影响很大,并且其中用到的数据库层吸引了社区的注意。很快,iBATISDatabase Layer 1.0项目启动了,其中包括两个组件:iBATIS DAO和iBATIS SQL Maps。2004年,iBATIS 2.0发布,它是在保持原有特性上的全新设计。Clinton将iBATIS这个名字和源代码都捐献给了Apache SoftwareFundation,这个项目在ASF一直持续了六年。最终,iBATIS DAO被抛弃了,因为有更好的DAO框架的出现,比如Spring Framework。
    在2010年5月19日,iBATIS 3.0发布了,同时,开发团队决定将项目转移到Google Code。由于iBATIS这个名称已经捐赠给了ASF,所以项目改用了新名称MyBatis。
    jQuery是一个多浏览器的JavaScript库,目的是简化客户端的HTML脚本编程。John Resig在2006年一月发布,目前有一个Dave Methvin领导的开发小组进行开发。在一万个最热门的网站中,他被65%的网站使用。jQuery是目前最流行的JavaScript库。
    MySQL是世界最广泛使用的开源关系数据库管理系统。它作为一个系统服务,允许多用户访问多个数据库。
    MySQL开发项目将代码依照GNUGeneral Public License发布。MySQL被一家瑞典的公司MySQLAB拥有并且提供赞助,这家公司现在隶属于Oracle。
    1.2 开展研究的意义图书推荐系统web平台用于支持图书内容的获取和图书推荐的展示。如果没有此平台,那么图书推荐系统就无法收集用户行为,统计用户喜好,展示推荐结果以及实现推荐系统的原有意义[5]。任何一个推荐系统都需要网络或者客户端平台来展示[6]。而构建这样一个后台服务器与浏览器客户端共同协作展示的平台,对于图书推荐系统具有重要的意义。
    1.3 论文研究内容本课题意图开发一套轻量、扩展性强、功能完善,性能良好的图书推荐系统。系统的最终目标是实现对图书内容进行展示、查询、用户登录注册,还有推荐信息的展示功能。用户通过浏览器与后台系统的Java Servlet、数据库交互,完成展示、查询与用户管理的目的。
    研究的着重点在于通过现有流行的开源系统、框架,来搭建轻量级的目标Web系统,在实现过程中更注重于代码结构与开发方法,所以并不追求功能的繁杂冗余。系统中数据来源于豆瓣,数据的抓取不是本课题研究的内容。为了使数据适用于Web系统,重新构建了表,并对数据进行了二次清洗。由于是展示平台,并不涉及图书推荐算法的实现与搜索功能的完善,所以搜索结果由数据库查询获得。但是在开发过程中为图书推荐与社会化图书搜索预留了接口,可以方便的加入这些功能。
    1.4 论文的组织结构
    第一章,文献综述。先讲了课题的背景,从图书推荐历史和技术发展史两个方面介绍,之后介绍了开展研究的意义,最后明确了研究的工作重点。第二章,框架与工具介绍。对Maven、Git等项目管理工具,Spring MVC、MyBatis、Bootstrap、jQuery等开发框架进行了介绍。第三章,基于Web的图书推荐系统展示平台的设计。对此Web系统所用到的数据库结构、页面原型、模块组成、总体的功能进行了论述。第四章,基于Web的图书推荐系统展示平台的实现。介绍了对数据的二次清洗、数据库的创建、数据的导入、工程的搭建、模块的开发以及测试等。并介绍了测试过程中做的一些性能优化工作。最后,结论。对本系统的优缺点做了一个总结,展望了一下未来的发展方向。
    2 框架与工具介绍2.1 Maven介绍2.1.1 Maven概述Maven是一个主要用于Java项目的自动化构建工具。Maven与Apache Ant工具具有相似的目的,但是他们是基于不同的理念并且以不同的方式来工作。Ant还可以用来构建和管理C#,Ruby,Scala以及其他的语言开发的项目,但是Maven原生并不支持这些。Maven项目由Apache Software Fundation托管,之前是Jakarta Project项目的一个部分[7]。
    Maven使用XML文件来描述需要构建的软件项目、项目的依赖模块和组件、构建顺序、目录结构以及需要的插件。他具有预定义的目标来实现确切的任务,比如编译源码并打包。
    Maven动态地从一个或多个库中下载Java库和Maven插件,比如Maven 2 Central Repository,并且将下载的内容保存到本地的缓存中。这个本地的下载缓存可以被本地项目更新,公共的库也可以被更新。
    Maven使用基于插件的体系来构建,他允许通过标准的输入控制任何的应用。理论上说,这将允许任何人为任何语言来在这个平台上编写插件。实际上,支持和使用除Java外其它语言的插件数量已经微乎其微。目前,只有支持.NET框架以及一个C/C++的原生插件还在被维护。
    2.1.2 Maven概念项目对象模型(POM):一个项目对象模型提供一个项目的所有配置属性。一般配置包括项目名称,owner和它的依赖。也可以通过插件,配置构建过程的各个阶段。比如说,用户可以配置编译插件,让它使用Java 1.5来编译,或者指定在一些单元测试失败以后,依旧打包工程。大的项目应该分成几个模块或者子项目,每个模块拥有自己的POM。用户可以编写一个根POM,通过这个根POM来使用一条命令来编译所有的模块。POM也可以从其它POM文件继承配置信息。所有的POM文件默认继承自Super POM。Super POM提供默认的配置,比如默认源代码目录、默认插件,等等。
    插件:大部分Maven的功能是通过插件来实现的。插件提供一组目标,并且可以通过以下的语法来执行:
    mvn [plugin-name]:[goal-name]
    比如说,一个Java项目可以使用compiler-plugin的complile-goal来编译:
    mvn compiler:compileMaven插件提供构建、测试、源码控制管理、运行web服务、生成Eclipse项目文件等等功能。插件由pom.xml文件中的<plugins>标签区域引入并且配置。
    然而,如果构建、测试、打包一个软件项目需要手动运行下面每条goal,那么,它将会变得非常笨重:
    mvn compiler:compilemvn surefire:testmvn jar:jarMaven的生命周期概念处理这类问题。
    插件是扩展Maven的主要方式。可以通过继承org.apache.maven.plugin.AbstractMojo类来开发Maven插件。
    构建生命周期:构建生命周期是一个用来给出为了达到goal所执行语句顺序的列表。Maven的一个标准生命周期叫做“默认生命周期”,它包括以下语句:

    process-resourcescompileprocess-test-resourcestest-compiletestpackageinstalldeploy
    插件提供的goal可以与不同的生命周期阶段相关联。比如说,默认情况下,“compiler:compile”的goal与编译阶段有关,而goal“surefire:test”与测试阶段有关。考虑下面的命令:
    mvn test
    当以上的命令被执行,Maven运行所有的在test语句之前的goal,直到test语句。在这种情况下,Maven运行“resources:resources”goal,然后是“compiler:compile”,等等,直到最终运行“surefire:test”goal。
    Maven也有标准用来清理项目和生成项目站点的语句。如果清理是默认生命周期的一部分,那么项目将在每次构建的时候被清理,这显然不是想要的。所以,清理有它自己的生命周期。
    标准的生命周期允许新用户能够通过一个简单的语句准确的构建、测试和安装每个Maven项目:
    mvn install依赖:Maven的依赖处理机制围绕一个坐标系统识别单个软件库或者模块。比如说,一个项目需要Hibernate库,仅仅需要在POM文件中声明Hibernate项目,Maven将自动下载依赖和Hibernate自身的依赖,并且把它们存储在本地的仓库中。Maven 2 CentralRepository是默认的用来搜索的库,但是也可以在POM文件中自定义仓库。
    还有一些其他的搜索引擎,比如说Maven Central,可以被用来去不同的开源库和框架中寻找模块。
    在同一个机器上开发的项目之间可以通过本地仓库来互相依赖对方。本地仓库是一个简单的目录结构,充当下载的依赖包的缓存与本地构建的项目的集中存储的地方。Maven命令mvn install构建一个项目,并且把它的二进制文件放到本地仓库中。然后其它的项目可以通过在POM文件中添加项目来引用这个工程。
    2.2 Git介绍2.2.1 Git概述Git是一个分布式的版本控制系统和源代码管理系统。它最初是Linus Torvalds为Linux内核开发而设计开发的,从那以后,Git就被许多其它的项目用来管理代码[8]。每个Git工作目录都是一个完整的仓库,保存着完整的历史和全部的版本跟踪能力,不需要依赖于网络连接或者一个中央服务器。Git是基于GNU GeneralPublic License version 2的开源软件。
    Git设计灵感来自于BitKeeper和Monotone。Git最初被设计成一个底层的版本控制系统引擎,在它上方其他人可以编写前端,比如说Cogito和StGIT。然而,核心的Git项目最终变成一个可以直接使用的完整的版本控制系统。由于受BitKeeper的严重影响,Torvalds故意地试着避免传统的方法,最终实现了一个独一无二的设计。
    2.2.2 Git特性Git的设计来源于Torvalds在开发Linux过程中积累的管理大的分布式项目的经验和对文件系统性能的知识,以及在短期内构建一个可以工作的系统的需求。这些影响导致了以下实现方案:
    1.对非线性开发的强大支持
    Git支持快速的分支和合并操作,并且包含专门的工具用来可视化和导航一个非线性的开发历史。Git的一个核心设想是:由于项目被分给好多个人来完成,修改会更多的被合并,而不是被写入。在Git中,分支是非常轻量的:一个分支仅仅是一次提交的关联,通过其父提交历史,整个分支结构就可以创建出来。
    2.分布式开发
    就像Darcs、BitKeeper、Mercurial、SVK、Bazaar和Monotone,Git给每个开发者一份整个开发历史的本地副本,并且修改记录会从一个这样的本地仓库复制给另一个。这些修改作为附加的开发分支导入进来,并且可以合并到本地的开发分支中。
    3.兼容已存在的系统和协议
    仓库可以通过HTTP、FTP、rsync或者通过建立在纯socket、ssh、或HTTP上的Git协议来推送。Git还有一个CVS服务器模拟,用来让使用CVS客户端和IDE插件的用户来使用Git仓库。Subversion和SVK仓库可以直接使用git-svn。
    4.高效处理大型项目
    Torvalds描述Git处理速度非常快、可扩展,并且Mozilla对其做的测试显示,它比一些版本控制系统快一个数量级,从本地存储的仓库获取比从远程服务器快一百倍。Git甚至在项目历史变得很大的时候速度依旧不会减慢。
    5.历史记录加密认证
    Git历史记录以这样一种方式存储:某次提交的id号取决于之前的完整的开发历史。一旦被发布了,就不可能再去修改旧的版本而不产生记录。这种结构就像一个hash树,但是在每个节点和叶子上,都有附加的数据。
    6.基于工具的设计
    Git是C语言程序,它有一些shell脚本来对其进行封装。尽管大部分的脚本为了提升速度和可移植性,已经用C语言重写过,但是依旧有一些并且很容易的就可以将组件和Git连接起来。
    7.可控的合并机制
    作为工具设计的一部分,Git具有对不能完成的合并有良好的定义模块,并且它还具有多种算法来完成合并。不能自动合并的部分它会最终提示用户,并且由用户来手动编辑。
    8.垃圾回收机制
    放弃等操作会在数据库中留下无用的对象。这是基本上是逐渐增长的历史记录的一个小碎片。Git将会在足够多的松散对象积累够之后,自动的进行垃圾回收。垃圾回收也可以使用git gc –prune命令来调用。
    9.定期对象包装
    Git将每个新创建的对象作为一个独立的文件来保存,尽管每个都会压缩,但是这依旧会占用大量的空间并且降低效率。这个问题是用一种叫做packfile的单一文件存储大量的对象在一个包中来解决的。每个packfile创建一个对应的索引文件,用来表示packfile中的每个对象的偏移地址。新创建的对象依旧独立存储,定期的打包,以保持空间利用率。打包仓库的过程会非常消耗计算机资源。相对于允许对象松散的存在于仓库中,git允许在空闲时间花费较大的开销来将他们打包。Git会定期的自动重新打包对象,不过,也可以使用git gc命令来手动打包。为了保证文件的完整性,每个packfile和它的索引内部都有SHA-1校验码,并且packfile的文件名包含一个SHA-1校验码。输入git fsck命令即可验证完整性。
    2.3 MVC模式与SpringFramework框架2.3.1 MVC模式MVC模式是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。
    MVC模式的目的是实现一种动态的程序设计,使后续对程序的修改和扩展简化,并且使程序某一部分的重复利用成为可能。除此之外,此模式通过对复杂度的简化,使程序结构更加直观[9]。软件系统通过对自身基本部分分离的同时也赋予了各个基本部分应有的功能。组件合作关系如图2.1所示。

    模型(Model):“数据模型”用于封装与应用程序的业务逻辑和数据处理相关的方法。“模型”有对数据直接访问的权力,例如对数据库的访问。“模型”不依赖于“视图”和“控制器”,也就是说,模型不关心它会被如何显示或是如何被操作。但是模型中的数据的变化一般会通过一种刷新机制被公布。为了实现这种机制,那些用于监视此模型的视图必须事先在此模型上注册,从而,视图可以了解数据模型上发生的变化。
    视图(View):视图层能够实现数据有目的的显示(理论上,这不是必需的)。在视图中一般没有程序上的逻辑。为了实现视图上的刷新功能,视图需要访问它监视的数据模型(Model),因此应该事先在被它监视的数据那里注册。
    控制器(Controller):控制器起到不同层面间的组织作用,用于控制应用程序的流程。它处理事件并做出响应。“事件”包括用户的行为和数据模型上的改变。
    2.3.2 Spring MVC框架Spring Framework提供它自己的MVC web应用程序框架,这并不是开发之初计划的。由于当时流行的Jakarta Struts web框架的设计很糟糕,并且其他框架也有许多不足之处,Spring开发者决定去编写他们自己的web框架。最重要的一点,他们觉得在表现与请求处理层之间没有完整的分离,并且请求处理层和模块层也没有完全分离。
    就像Struts,Spring MVC也是一个基于请求的框架。每个接口的目的是让Spring MVC用户能够清晰、容易的实现MVC框架的应用。所有的接口都是与Servlet API紧耦合的[10]。这个对Servlet API的紧耦合被某些开发者看做是Spring的失败,它不能提供一个高层次的对基于web的应用的抽象层。然而,这样耦合确保了Servlet API能够给开发者提供一个高度抽象的框架来使得开发变得容易[11]。
    Spring MVC框架围绕一个核心servlet来给控制器分发请求并且提供一些其它的功能来方便web应用的开发,Spring的DispatcherServlet就是用来干这些的。它将Spring IoC容器完全集成在一起,这样你就可以使用所有其它的Spring特性。
    Spring Web MVC DispatcherServlet的请求处理工作流如图2.2所示。DispatcherServlet是一种“Front Controller”设计模式的表现(这种设计模式在许多其它的web框架中也很常见)。

    DispatcherServlet是一个真实存在的Servlet(它继承自HttpServlet基类),并且在web应用的web.xml文件中声明。那些你想要DispatcherServlet处理的请求必须在同一个web.xml文件中使用URL映射来映射到这个Servlet。这是标准的J2EE servlet配置。图2.3是对DispatcherServlet的声明和映射。

    在上面的配置示例中,所有的以.form结尾的请求都会被”example” DispatcherServlet来处理。这仅仅是配置Spring Web MVC的第一步。
    在Web MVC框架中,每个DispatcherServlet都有自己的WebApplicationContext,这个WebApplicationContext继承了根WebApplicationContext定义的所有的beans。图2.4为Spring Web MVC中的上下文层次关系。

    框架将会在初始化DispatcherServlet的时候,在你web应用的WEB-INF目录中寻找一个名字叫[servlet-name]-servlet.xml的文件,然后创建在那个文件里定义的beans。
    WebApplicationContext是一个对单纯的ApplicationContext的扩展,对web应用增加了一些额外的特性。它区别于普通的ApplicationContext的地方是,它可以解析主题,并且它知道自己与哪个servlet相关联。
    Spring DispatcherServlet有一些特定的beans来实现处理请求和获取正确的视图返回给用户。这些beans被包括在Spring框架中并且能在WebApplicationContext中配置,就像其它的beans一样的配置方式。
    一些比较重要的接口定义以及他们的功能列举如下:

    HandlerMapping:选择对象来处理来自内部或者外部的请求。HandlerAdapter:对请求进行处理。Controller:介于Model和View之间来管理请求和返回合适的响应。他就像一个门,来对传入的请求进行处理。它从Model获取数据,传递给View并将View展现出来。View:用于向客户端返回数据。一些请求可能会直接请求到视图,而不经过模块部分,其它的请求三者都要经过。ViewResolver:根据逻辑名称为视图选择一个View。LocaleResolver:获取并且保存用户的位置信息。MultipartResolver:提供对文件上传的请求的封装。
    以上的每个接口对整个框架都有重要的作用。这些接口提供的抽象层用处非常大,所有继承自这些接口的类都按照同样的特性建立在Servlet API之上。开发者可以自由的去编写他们的实现类。Spring MVC使用Java的java.util.Map接口作为数据Model,数据Model与View共同生成最终的返回页面[12]。
    2.4 MyBatis介绍MyBatis是一个在Java和.NET平台的持久层框架,它将SQL声明与存储过程使用XML描述[13]。MyBatis是一个自由软件,基于Apache License 2.0发布,它的原名为iBATIS[14]。
    MyBatis允许你可以使用所有的数据库功能,比如存储过程、视图、任何复杂的查询,以及数据库厂商所专有的功能。它经常被用来处理遗留数据库、规范化的数据库或者是需要完全的控制SQL执行的时候。
    它相对于JDBC简化了编码,SQL语句仅仅需要一行代码便可以执行。这样便节约了时间,避免了一些常见的失误,比如忘记关闭数据库连接、编写了一个错误的数据映射、超过了结果集的数量限制或者是查询结果不仅仅是一条。
    MyBatis提供了一种映射引擎,将SQL结果映射到对象树中。SQL语句可以通过使用内置的有XML类似语法的语言来动态的建立,或者通过使用Velocity集成插件来使用Apache Velocity建立。MyBatis集成在Spring Framework和Google Guice中,这个特性可以允许自由的构建商业应用并且不需要调用任何的MyBatis接口。
    MyBatis支持声明式的数据缓存。MyBatis集成了:OSCache,EHcache,Hazelcast和Memcached,并且支持集成自定义的缓存工具。
    MyBatis提供一套自动生成代码的工具:MyBatis Generator。MyBatis Generator将会检查一个数据库表(或者许多表),然后生成MyBatis项目用来支持增删改查操作。
    2.5 Bootstrap介绍2.5.1 Bootstrap概述Bootstrap是一个用来创建网站和web应用的工具集。它包含许多基于HTML和CSS的设计模板:排版、表单、按钮、图表、导航、其他的界面组件以及可选的JavaScript扩展。它是GitHub上最流行的项目,NASA和MSNBC网站都在使用此框架。
    Bootstrap有相对不完整的对HTML 5和CSS 3的支持,但是它兼容所有的主流浏览器。从2.0版本开始,它开始支持响应式设计。这意味着网站页面的视图设计根据设备的特点来动态的调整。
    Bootstrap是Twitter发布在GitHub上的开源软件。开发者可以参与项目并且对平台贡献自己的力量。
    2.5.2 结构和功能Bootstrap是模块化的,包含一系列的LESS样式,用来实现工具的不同部件。一个叫做bootstrap.less的样式表包含了组件的样式。开发者可以选择自己需要的组件来创建自己的bootstrap.less文件。使用LESS样式表语言允许用户使用变量、函数、运算符、嵌套选择器,以及所谓的mixins。
    自从2.0版本开始,在Bootstrap网站上添加了自定义的配置选项。开发者可以在一个表单上选择想要的组件和调整参数,然后生成配置好的预编译CSS样式表。
    1.栅格系统和响应式设计
    Bootstrap默认支持940像素宽度的栅格布局。另外,开发者可以使用一个可变宽度的布局。工具集中有四个参数来使用不同的分辨率和设备类型:手机、竖屏、横屏格式、平板电脑、有低分辨率和高分辨率的电脑。页面会自动根据这些分辨率来调整列的宽度。
    2.CSS样式表
    Bootstrap提供一系列的样式表来为主要的HTML组件提供基本的样式定义。这样可以对不同的浏览器和不同宽度的设备提供一套统一的文字格式、表格、表单项目布局。
    3.可重用的组件
    除了常用的HTML元素,Bootstrap包含一些其它的样式元素:带有高级特性的按钮(比如按钮组、带有下拉选择的按钮、导航列表、横向、纵向的标签、面包屑导航、分页等等)、标签、高级的排版功能、缩略图、带格式的警告信息以及进度条。
    4.JavaScript插件
    JavaScript组件基于jQuery JavaScript库。它提供附加的用户界面元素,比如对话框、工具提示以及轮播。他们也扩展了一些已经存在的页面元素,包括:一个输入框自动完成的功能。
    2.6 jQuery介绍jQuery是一套跨浏览器的JavaScript库,简化HTML与JavaScript之间的操作。目前是由 Dave Methvin 领导的开发团队进行开发。全球前10000个访问最高的网站中,有59%使用了jQuery,是目前最受欢迎的JavaScript库[15]。
    jQuery 是开源软件,使用MIT许可证授权。jQuery的语法设计使得许多操作变得容易,如操作文档对象、选择DOM元素、创建动画效果、处理事件、以及开发Ajax程序。jQuery 也提供了给开发人员在其上创建插件的能力。这使开发人员可以对底层交互与动画、高级效果和高级主题化的组件进行抽象化。模块化的方式使 jQuery 函数库能够创建功能强大的动态网页以及网络应用程序[16]。
    jQuery有下列特色:

    跨浏览器的DOM元素选择DOM巡访与更改:支持CSS 1-3事件(Events)CSS操纵特效和动画(移动显示位置、淡入、淡出)Ajax延伸性(Extensibility)工具:例如浏览器版本(已取消自带,改由jQuery Migrate plugin外挂提供)和each函数。JavaScript插件轻量级
    jQuery 1.8.0版时(自带Sizzle.js):



    文件
    行数(行)
    大小(KB)




    jquery-1.8.0.min.js
    2
    91


    jquery-1.8.0.js
    9228
    254




    DHTML DOM选择器与链式语法
    经由jQuery的DHTML DOM选择器,可以更容易的操作在复杂的树状HTML中的任何DHTML DOM对象,并可用链式语法对同一对象的不同属性进行操作。
    例如:
    $("p.surprise").addClass("ohmy").show("slow");
    相当于:查找HTML的<p>标签,且其class为”surprise”的DHTML DOM对象,将其Class属性多加上一个”ohmy”(通常是配CSS的定义做显示时的配色修改),打开显示。


    CSS 1-3选择器:支持CSS选择器选定DOM对象。跨浏览器:跨浏览器的AJAX解决方式,支持InternetExplorer 6.0+、Opera 9.0+、Firefox 2+、Safari 2.0+、Google Chrome 1.0+简单:较其它JavaScript库更易于入门。
    2.7 小结本章简要介绍了开发过程中用到的几个比较重要的工具与框架。在项目创建和最后的部署过程中,Maven工具为项目的依赖的解决和编译打包提供了很好的解决方案。而在项目开发过程中,Git工具为代码管理、版本控制以及进度统计,都提供了很大的帮助[17]。
    本章还介绍了四种框架:Spring MVC、MyBatis、Bootstrap和jQuery。前两者是后台框架,提供了一系列的类与接口,用于实现系统功能、简化与美化代码结构设计,并提供了一些工具来方便开发者实现相应的功能[18]。后两者为前台框架,简化了前台的开发过程,提供封装好的方法来让前台交互更加方便,界面更加友好。这四种框架为开发的核心框架,它们极大的提高了生产率,并且使最终的产品不仅内部结构,而且外部表现都健壮、美观。
    3 基于Web的图书推荐系统展示平台设计3.1 系统总体设计3.1.1 总体功能描述本课题是要开发一套轻量级、功能完善的基于Web的图书推荐系统展示平台。利用此平台,对图书信息进行展示与推荐,对用户提供注册与登录功能。由于原始数据为从豆瓣依照网站网页结构抓取得来,并不适用于系统直接读取,于是需要将原始数据表格进行重新设计,遍历分离所需数据存入新设计的表中,使其可以更便捷的查询与处理。开发过程需要考虑Spring MVC框架,将功能按照模块、视图、控制器三部分分离,模块与视图适度的模块化使其可以较好的重用。beans使用注解来注入,这样可以提高小的个人项目的开发效率。在开发开始,配置Maven来解决需要的依赖包,创建Git仓库,来控制版本。
    3.1.2 系统模块组成首先将系统在Spring MVC的基础上分为了三层,分别为:Web层,服务及模块层,数据层,而Web层中,分为Controller与View模块,View为Controller服务,按照预定义的格式来展示Controller的数据。Controller将数据访问与一些公共的逻辑算法交给Model来处理,Model将处理结果交还给Controller。而Model类就像前面Controller给他的任务,负责与界面无关的逻辑计算与数据库的访问、以及其它格式数据的获取。结构如图3.1所示。

    Web层负责处理用户的请求,其中Controller会接收DispatcherServlet分发过来的请求,Controller调用服务与模块层中的模块,进过逻辑计算,生成最终的数据,将数据通过键值对的方式,将视图通过字符串方式传递给DispatcherServlet,DispatcherServlet再读取对应的View,使用View作为模板,生成最终的页面,返回给用户。
    服务与模块层负责逻辑计算与数据获取。其中MyBatis Mapping模块为通过MyBatis Generator自动生成的DAO类,以及自定义的DAO类,用于连接MySQL数据库并且执行增删改查操作。而逻辑计算与数据获取模块包含了公共方法类,某些特殊的算法计算,以及对配置文件的查询取值。
    最底下的数据层,包括数据库系统与文件系统,是用来存储数据与配置的层。其中数据库采用MySQL数据库,配置文件使用Java自带的.properties文件。
    三层之间是互相独立的,只有最近的两层之间可以访问:服务与模块层只可以访问数据层,而Web层只可以调用服务与模块层。其中,服务与模块层中的逻辑计算与数据获取模块,每个模块之间是相互独立的,模块与模块之间不可以互相访问,这样用来降低耦合性,每个模块完成一个完整的任务。由于Web请求的模式决定Web应用只能是被动接收请求,并且Web应用没有涉及费时的网络获取,在代码中没有回调函数,所以层与层之间的调用为单向的,即模块层只可调用数据层,让数据层来执行操作,然后返回数据给模块,Web层调用模块层,将一些逻辑计算与数据获取的过程交给模块来完成,结果返回给Web层,而不可能模块层主动的调用Web层,来对其中的值进行更改,而后返回给用户一个新的页面。在Web层中,View模块只负责对数据进行格式化,生成最终用户页面,因此,它只接收Controller模块的值,而与程序的其它层次模块之间不可以通信。这样,在编写Controller模块时,并不需要了解数据库的组织结构以及配置文件的具体文件名等,只需要知道调用哪个模块,将需要的参数传入模块,模块返回的值就是所需要的数据。而在编写View模块时,也不需要了解其它各个层次都有什么作用,只需要分析页面哪个部分的数据是动态获取的,然后将此部分数据安排给Controller,让Controller传过来就可以了,这样将不同模块之间的耦合性降到了最低。而Controller就像一个乐队的指挥,按照需求调用各个模块,让系统的各个部分井然有序的工作。
    3.2 模块详细设计3.2.1 数据库设计1.原始数据库
    原始数据库存储从豆瓣抓取下来的数据,分为三张表:book_author_info,book_online_info,book_publishing_info。
    book_author_info表存储作者信息,分为图书编号、作者姓名、作者简介与介绍四列。作者简介内容包括作者的生平以及与此书和作者都有关的一些事件介绍,介绍列与前者相同。作者姓名列存储了各个作者加国籍的信息,需要进行清洗。
    book_online_info表存储图书的一些社会化信息,分为图书编号、标签、访问次数、5星评价数量、4星评价数量、3星评价数量、2星评价数量、1星评价数量、想读用户数量、在读用户数量、已读用户数量、还想阅读的书 这十二列。其中标签列将所有的标签,带上打标签的次数都放到了同一个字符串中,需要对其清洗。
    book_publishing_info表存储了图书的出版信息,分为图书编号、ISBN号、书名、作者姓名、图书描述、图书目录、包装类型、定价、总页数、出版社名称、出版时间以及图书封面缩略图名称这十二列。作者姓名与book_author_info表的作者姓名列重复,而定价、总页数等数据使用varchar来存储,出版社也没有构建序号,不便于检索,所以这些内容都需要清洗整理。
    2.清洗后数据库
    清洗后的数据库是可以直接拿来网站使用的,从原始数据库中数据清洗整合出来的数据,分为九张表:book_info,tag_info,book_tag_relation,author_info,book_author_relation,nationality_info,publisher_info,binding_type,book_relation,内容如表3.1-3.9所示。
    book_info表内容



    表名称
    书籍详细信息表




    列名称
    图书编号、ISBN号、书名、书籍描述、书籍目录、包装类型序号、定价、页数、出版社序号、出版时间、封面图片名称、评论数量、5星评价数量、4星评价数量、3星评价数量、2星评价数量、1星评价数量、想读人数、正在读人数、已读人数


    描述
    其中书名数据类型由原来的text转换为varchar,便于构建索引,提高查询速度。将原先耦合在一起的包装、出版社数据独立出来,只在表中引入其序号,便于按不同类型来查询。将定价和页数的数据类型由varchar改为int,方便程序读取,提高查询速度。定价数据类型并没有采用double,因为它是固定的两位小数,精确到分即可,并没有必要采用比较大的double类型。



    tag_info表内容



    表名称
    标签内容表




    列名称
    标签序号、标签名称、标签数量以及标签等级


    描述
    标签序号为自动生成的这个标签唯一的序号,用来识别标签。标签名称是清洗出的单个的标签,使用varchar存储,便于生成索引,提高搜索速度,标签数量为在所有图书的标签中,标签出现的次数。



    book_tag_relation表内容



    表名称
    图书与标签关系表




    列名称
    图书编号、标签序号以及使用次数


    描述
    图书编号为book_info表中的主键,标签序号为tag_info表中的主键,使用次数为给这本书打这个标签的用户的数量。



    author_info表内容



    表名称
    作者信息表




    列名称
    作者编号、作者姓名、作者简介内容、作者的国籍编号


    描述
    作者编号为自动生成,是表中的主键,作者姓名是将原始数据中的作者姓名清洗出单个的作者后,放入表中的。改用varchar存储,便于生成索引。国籍编号为nationality_info的主键。



    book_author_relation表内容



    表名称
    书籍与作者关联表




    列名称
    图书编号、作者编号


    描述
    图书编号对应book_info表主键,作者编号对应author_info表主键。



    nationality_info表内容



    表名称
    国籍信息表




    列名称
    国籍编号、国籍名称


    描述
    国籍编号为自动生成的主键,国籍名称为从作者信息中解析出来的作者的国籍。



    publisher_info表内容



    表名称
    出版社信息表




    列名称
    出版社的序号、出版社名称


    描述
    出版社序号为自动生成的主键,出版社名称为从原始数据中清洗出来,转换为varchar格式的数据,方便生成索引以提高查询速度。



    binding_type表内容



    表名称
    包装类型信息表




    列名称
    包装类型序号、包装类型名称


    描述
    包装类型序号为自动生成的主键,而包装类型名称为从原始数据中清洗出来,转换为varchar类型的数据。



    book_relation表内容



    表名称
    相关借阅信息表




    列名称
    书籍编号、相关借阅书籍编号、相关借阅书籍名称


    描述
    书籍编号与相关书籍借阅编号都来自于book_info的主键。而相关借阅书籍名称为书籍的字符串名称,因为有部分相关借阅书籍可能不在数据库中,所以有必要将其字符串名称存于表中,可以拿去搜索或者进行其他一些操作。



    3.用户数据
    用户数据是用户的登陆注册以及访问记录的表。其中的数据为Web应用自己生成,而不是预先装入系统之中的,分为:user_info,user_visit_history,user_search_history,内容如表3.10-3.12所示。
    user_info表内容



    表名称
    用户信息表




    列名称
    用户编号、用户名、密码、用户邮箱、用户状态、用户口令


    描述
    其中用户编号为自动生成的主键,用户名为用户注册时所起的名称,与密码一起用于登录。密码使用SHA算法加密,用户邮箱为注册时填写,用于激活账户与找回密码之用。用户状态分三种:0:已注册,未激活,1:注册并激活,2:密码已丢失,需要重新找回。用户口令取自第一次登录的session id,用于保持用户持久登录状态。



    user_visit_history表内容



    表名称
    用户访问记录表




    列名称
    用户编号、用户访问图书编号、访问时间、访问来源、访问来源图书编号


    描述
    用户编号对应user_info表中的主键,用户访问图书编号为用户打开的图书介绍页面中图书的编号,访问时间为打开图书介绍页面的时间,访问来源分三类:0:首页,1:搜索结果,2:其它图书推荐,其中访问来源为2的,需要填写访问来源图书编号。



    user_search_history表内容



    表名称
    用户搜索记录表




    列名称
    用户编号、用户搜索关键词、搜索时间


    描述
    用户编号对应user_info表中的主键,搜索关键词为用户在搜索框中输入的关键词,搜索时间为发生搜索事件的时间。



    3.2.2 页面原型设计使用快速原型工具Axure RP Pro,根据功能设计了需要实现的页面的原型:首页、搜索结果页、图书展示页、注册页面、登录页面。生成的首页原型效果如图3.2所示。

    首页顶部包含一个logo,链接到本页;搜索框,提供对图书的搜索入口。以及登陆和注册链接,用于用户的注册与登录管理。正文部分分为四个大模块,名称分别为:相关图书推荐、分类热门图书、图书分类以及图书排行。图书推荐模块列出单本书的缩略图与简要介绍,而分类热门模块列出了几个大的图书分类,以及几本此分类下的比较热门的图书。图书分类模块列出了所有的图书分类,用户可以直接点入查看此分类下的所有图书。图书排行列出前九本最热门的图书。页脚部分注明页面版权信息,以及创建年份。
    搜索页面页眉页脚与首页相同,正文部分为一个搜索结果列表,显示搜索结果中的15条记录,每条记录显示图书缩略图、书名、作者、出版社、出版日期、价格以及评分。正文底部是分页,列出了结果的页数,可通过点击来查看后面的搜索结果。右侧列出了热门图书列表,用于向用户推荐。
    图书展示页面页眉页脚与首页也相同,正文部分分四大块,第一块为图书基本信息,包括标题、缩略图以及一些图书在版编目信息:作者、出版社、出版时间、页数、定价、装帧、ISBN号。同时还显示评分以及各个星评分数量。第二块为内容简介,是对书内容的简要介绍以及图书目录。第三块为作者简介,是对图书作者以及译者的简要介绍。第四块为相关推荐,展示阅读此书的人还阅读了的书籍。由于原始数据并不包含用户评论,因此用户评论的内容并没有加入展示。
    注册页面和登录页面较为简单,用户填写用户名、邮箱、密码、确认密码,之后点击注册即可。登录时,用户输入用户名、密码,然后登录系统,会跳转到首页,首页右侧顶端登录注册不再显示,改为用户名与退出。
    3.3 小结本章介绍了本课题所研究的基于Web的图书推荐系统展示平台的系统总体设计与详细设计。总体设计主要讲述了系统设计的层次结构,并且规定了系统各层各模块之间的组织结构与通信规则。详细设计部分对数据库进行了设计,列出了各个表的结构与描述。并且使用页面原型工具构建了简单的页面,来作为之后页面开发与功能模块划分的依据。总体合计与详细设计在系统开发之前,对之后系统的结构与功能都起到非常重要的影响,结构的好坏直接影响到系统的性能。此章是下一步实现系统的必要步骤和重要依据。
    4 基于Web的图书推荐系统展示平台实现4.1 环境的搭建4.1.1 数据库的建立与数据的导入PowerDesigner创建一个物理数据模型,配置好数据库类型,添加表结构,将在详细设计中所设计的数据库信息与列信息、自增列属性输入到表结构中,生成如图4.1所示的物理结构设计图。

    使用其自动生成工具,生成用于创建数据库的SQL脚本。之后,使用MySQL工具导入到数据库中。同时,将原始数据也使用MySQL工具导入到数据库中。
    4.1.2 工程建立使用Eclipse创建一个Spring MVC项目,系统会自动生成一套目录结构,如图4.2所示。


    src/main/java目录用来存放项目的主体部分的源代码,所有的Controller模块、Model模块,以及DAO的Java类,都放在这里,在发布的时候,这里的源码会在编译成class文件后,放入WEB-INF目录下的classes目录。src/main/resources目录用来存放项目的配置文件以及MyBatis的Mapping文件。在部署过程中,也会被放入WEB-INF目录下的classes目录中。src/test/java目录用来存放项目的测试类,src/test/resource目录用来存放项目的测试配置文件,这些都会在部署时,放入WEB-INF目录下的test-classes目录中。JRE System Library包含系统中安装的JRE的库,在项目创建时,可以选择版本。Maven Dependencies包含了在Maven的POM配置文件中所配置的依赖包,这些包在工程创建时,由Maven从Maven仓库中下载到本地缓存,并且链接到工程中。src目录分main和test,而main/webapp目录下有resources与WEB-INF目录,其中resources目录是在servlet-context.xml中配置的,用于存放页面中的资源的目录,分为css、img、js三个目录,WEB-INF目录分为classes、spring与views目录以及web.xml文件,web.xml文件为Java Servlet的标准配置文件,Spring就在这里配置进去。classes为应用发布时,.class文件的目录,spring目录为spring配置文件存放的目录,用于修改配置,添加beans用于注入等。views目录为视图模块存放的地方,使用jsp作为视图文件。target目录为自动编译的目录,目录中有所有类、测试类的编译结果.class文件,以及Maven的配置文件pom.xml。pom.xml文件为Maven的配置文件,它包含了项目的基本配置、依赖包以及插件配置。项目创建时,默认只有Spring MVC的基本配置。
    4.1.3 版本控制为了开发的方便,防止不必要的损失以及对进度的掌控,项目一开始便进行了版本控制,在GitHub网站创建了一个私有仓库,项目的根目录下初始化了Git本地仓库,配置好全局变量:用户名、邮箱以及SSH key,项目目录下加入远程的GitHub仓库,之后便可以添加文件、提交更改并且推送到远程服务器上。
    修改提交的节点选择在每次一个功能完成之后或者是对之前的文件需要进行比较大的修改之前。功能完成之后提交,可以确保自己能够定期的跟踪到完整的可运行的项目,不至于两次提交之间项目变化过大,如果想要修改,那就不容易找到一个合适的检出时间点。进行比较大的修改之前提交,虽然可能一个功能并没有完成,但是修改过程中可能要参考到之前的部分功能,所以仍有必要将其保存提交。在修改完之后,最终确定了用新的方案,再次提交。
    4.1.4 MyBatis配置MyBatis官方提供了一个自动生成代码的工具:MyBatis Generator(MBG)。它会检测数据库的所有表,并且生成可以用来访问数据库表的代码。这样可以减轻最初访问数据库所需编写代码的工作量。MBG提供了所有常用的数据库操作:增删改查。对于单表的操作,只需要使用这一套生成的类即可,生成结果如图4.3所示。

    在生成的文件中,dao为mapper接口,存放TableNameMapper.java文件,用来在配置中注入或者使用SqlSession来获取实例,对数据库表执行增删改查操作。model为表结构的类TableName.java和查询条件构造类TableNameExample.java,TableName.java用于存储对应表的响应条目值,用来实现update和insert操作,以及查询出结果的存储。TableNameExample.java用来构造where语句,用于执行select操作。在数据库中有多于两个列的类型为TEXT或者BLOB,那么除了生成TableName.java,还会生成一种TableNameWithBLOBs.java的文件,其中TableName.java负责存储一般的数据类型,TableNameWithBLOBs.java文件负责存储TableName.java中所有数据之外,还包括了TEXT和BLOB类型的数据。mapper目录存储xml配置文件,用于支持在TableNameMapper.java文件中定义的操作。
    将代码和配置文件加入到工程中之后,会出现编译错误,显示一些引用的类不存在于工程中。查找原因,发现是因为项目中没有加入数据库与MyBatis的依赖,修改pom.xml配置文件,加入spring-jdbc、mybatis、mybatis-spring与mysql-connector-java依赖配置。
    在src/main/resources加入mybatis-config.xml,用来为MyBatis提供连接数据库的配置与Mapper类集合的配置。创建Mysql.properties文件,将配置写入文件中以便复用。
    4.2 数据的清洗由于原始数据并不能直接拿来使用,因此需要按照之前设计的数据库,将三个表中的原始数据清洗后,存入新设计的表中,程序流程如图4.5所示。
    程序会先定义起始和终止图书编号,之后,从第一个图书编号开始,通过主键,查询到图书的数据,将需要的值取出,比如作者信息。作者信息包括了作者国籍、作者姓名以及其它作者姓名,格式如图4.4所示。

    开始打算通过一个完整的正则表达式来对作者信息进行解析,但是由于Java的正则表达式并不能够分辨出中文标点与中文文字,因此,先对字符串做初步的清理:将“编著”、“译者”、“主编”替换为英文斜杠“/”来区分不同的作者,将中英文逗号、中英文括号等内容都替换为英文分号“;”,用于区分国籍与作者。之后,使用将字符串按照斜杠来分割成一个字符串数组,数组中每个字符串代表一个可能带有国家信息的作者名称,使用正则表达式:”^(;([\u4e00-\u9fa5]+);)?([\w\u4e00-\u9fa5\.•]+)”来匹配,取出可能以分号加中文字符开头的,作为国籍,以英文字符或者中文字符组成的连续的词作为作者姓名。
    之后,拿国籍信息去国籍信息表中查询,没有此项,则作为一个新的条目插入,有则不做处理。在有的数据清洗过程中,比如标签,它有一个统计数据,那么如果表中有这个标签,会将统计数据增加一个。作者姓名与国籍类似,采用相同的方法来存储。之后便查询下一条图书记录。

    为了加快读取速度,每次的读取并不只是一条,而是多条图书数据一起读取出来,这样会一次将较多的数据调入内存,降低磁盘IO操作,加快速度。可是由于每本书有三到五个作者,每个作者和国籍都会在解析出来之后变为一个独立的需要插入到数据库中或者去数据库中查询的条目,随着数据条数的增多,同时提交的事务数量会加倍增长,MySQL系统就出现了session数量不足的错误。于是,将每次取出的条数减少,并且在每次操作完成一组数据后,提交并关闭数据库,在需要操作前,再打开数据库。这样就能够即时的关闭用完的session,不会出现由于大量已结束的事务占用session而报错的问题。
    同样为了加快数据清洗速度,使用两台电脑,一台运行数据库系统,另一台运行Java程序,MySQL系统打开网络用户的访问权限和所在系统的防火墙3306端口,另一台连接并处理数据。由于数据库操作占比较大的时间,所以瓶颈依旧在运行数据库的系统中,不过相对与在同一个系统中,CPU占用和内存占用有一定程度的下降。
    4.3 系统开发按照总体设计阶段的分层,将系统分为三个包:cn.edu.ustb.controller、cn.edu.ustb.dm、cn.edu.ustb.model。如图4.6所示。

    4.3.1 控制器类controller包为系统结构中的controller模块,根据功能,划分为了五个类:BookInfoController.java负责图书详细信息的展示,IndexController.java负责首页的视图内容获取展示,LoginController.java负责登录信息的处理,RegisterListController.java负责对注册信息的处理,ResultListController.java负责处理查询。
    controller类将SqlSessionFactory使用注解的方式注入类中,并且使用注解来实现Controller类与请求映射。使用Log4j工具来输出日志。借鉴Objective-C的方式,使用setter/getter方法来获取变量,以便延迟加载以及提高利用率。代码结构如图4.7所示。

    在最开始,Mapper都是使用注解来注入进来的,但是发现SqlSession的开启与关闭不受到控制,完全靠系统来自动完成,那么在并发数量过大之后,大量线程占用session,很容易出现session数量过多的问题,其他人访问不了网站。将Mapper的获取方法改为了使用SqlSession的getMapper方法来获取,这样就可以完全控制session开启时间、结束时间。在每次访问页面的时候,包括浏览器会话没有关闭时刷新,都会重新开启一个新的SqlSession,获取新的Mapper实例,然后执行数据库操作,最后,关闭数据库连接。这样,能够即时的回收过期的SqlSession,防止大量无用的session占用数据库资源。
    在使用getter/setter方法时,刚开始将SqlSession的获取放到了getter中,这样会首先检测有没有实例,没有实例再创建,意图是为了延迟加载,在用到的地方才初始化它,并且防止每次使用都创建新实例。可是在实际中却发现,如果用户刷新页面,或者点击分页按钮,系统会抛出错误,说数据库已关闭,无法执行查询操作。原来每次访问,在浏览器没有结束会话时,Web容器会将Controller类的实例保存在内存中,而每次请求只会执行RequestMapping所指定的函数。于是修改SqlSession的获取方式,在浏览器每次发起请求时,通过SqlSessionFactory类的openSession函数来获取一个SqlSession实例。
    Mapper的获取也受到了影响,因为每次访问都会创建一个新的SqlSession实例,那么Mapper如果不为null的话,就不会重新创建Mapper实例,这样,Mapper的SqlSession将是已经关闭的session,它不能够执行任何数据库操作。因此,在每次访问时,会将所有的Mapper都重置为null,以使其重新初始化。
    4.3.2 模块类模块类包含一些页面需要的数据结构,对数据的加工函数以及分页功能实现。其中,BookClassifyItemModel.java类为单纯的书籍按照分类来显示信息的模块,其中,借鉴了MyBatis的Example类的方法,添加了一个内部类,在父类中编写了创建内部类的函数,用于创建图书列表。
    BookListItemModel.java类为图书的基本信息展示类,用在了图书推荐、图书排行、查询结果展示以及图书详细信息中。在类中提供了计算得分的函数,以及格式化日期的函数,用于在页面中显示。
    SearchResultPaginationModel.java类为查询结果分页模块,用于支持查询的分页显示以及分页功能。由于数据量巨大,为了提高查询效率,分页查询并没有采用MyBatis的分页查询方法,MyBatis会在第一次查询时,将所有的符合条件的结果读入内存中,之后再根据分页条件来显示,这样,虽然会在页面跳转的时候很快,但在第一次查询时,会有大量的磁盘IO操作,在数据类大的情况下,会对系统性能造成很大的影响,而搜索结果大部分用户只是关注前几页,后面的结果访问量并不大,这样就有些得不偿失。分页查询采用数据库的limit条件,只在每次查询时获取每一页要显示的数据,在创建了索引以后,这个查询过程是非常迅速的,只将需要的数据读入内存。查询效率提高了,就需要自己来实现分页。
    4.3.3 视图类视图使用jsp作为页面,引入了JSTL的c库来辅助生成布局。视图文件如图4.8所示。

    header.jsp为页面顶端的logo、搜索栏与登录注册按钮的部分。footer.jsp为页面底部版权信息的内容。pagination.jsp为分页,根据SearchResultPaginationModel.java的内容来生成分页。
    bookInfo.jsp负责显示图书的详细内容,index.jsp负责首页内容的显示,login和register负责登录与注册页面,resultList为搜索结果,只是单纯的搜索结果列表,用于分页时,通过AJAX请求来局部刷新,减少流量。resultListPag为搜索结果页面,是页面的框架,其中引入了resultList,作为第一次访问时,搜索结果的展示。
    每个页面都引入header.jsp与footer.jsp,用来引入所需要的布局文件与页面脚本,构建起基本的页面框架。页面导航栏的布局采用Bootstrap的导航栏样式,登录可以从导航栏上直接输入来登录。搜索条件分为标题、作者、出版社,可以对这三者进行查询。
    采用JSTL的标准c标签库,方便的实现循环(c:forEach)、判断(c:if、c:when),
    页面整体布局采用Bootstrap的响应式布局,首页、搜索结果页将正文部分分为左右两块,图书信息页面只有一个块。在首页中各个块中,每本书作为一个row类型,每个row又分为两个span,用于分割左右两块。一部分布局是由自定义的main.css文件来定制。而自定义的JavaScript也由在footer中引入的main.js来定制那些比如搜索按钮点击事件、分页按钮点击事件、登录等等。
    分页按钮的样式采用了Bootstrap的分页按钮,参考Amazon查询结果的分页效果,在页数多于9页的情况下,翻到中部,则只显示部分挨着的页码,结合SearchResultPaginationModel类的结构,完成了查询的分页。
    4.4 分析及调优首页为所有页面中最为复杂的页面,需要查询四块内容,页面效果如图4.9所示。

    四块内容分别需要按照各自的查询条件来排序,然后取前几个符合条件的结果。在刚开始,没有对数据库优化之前,页面打开速度几乎需要3秒,对查询SQL进行分析,发现,大部分操作时间都消耗在了排序上,于是对排序条件创建了索引,首页的首次打开延迟变得小于1秒,并且由于在控制器中使用getter\setter,部分没有参数的数据会在查询之后一直留在内存中,不会进行第二次查询,所以刷新会返回304,页面内容没有改变。

    图4.10为查询结果页面。由于此平台不涉及搜索算法的研究,所以搜索结果为从数据库字符串中like出来的。考虑到数据库巨大,若不做处理,将会严重影响查询效率。于是按照查询特点,对图书标题、出版日期两列做了索引,查询速度有了成倍的提升。再加上每次查询使用limit,磁盘内存间的交换操作减少了许多。

    图4.11为图书信息的展示页面,对图书的缩略图、内容以及作者简介做了展示,还有登录失败后会跳转到的登录页面,注册按钮点击后跳转到的注册页面,相对于其它两个相对功能较单一,性能也没有太大的提升空间。
    4.5 性能测试使用Chrome浏览器的开发者工具来进行测试。结果如图4.12所示,首次加载首页需要等待6ms,其它静态的css与js文件几乎不需要时间,在统计结果中,显示为0。之后刷新,Tomcat会从内存中直接取得返回结果,统计如图4.13所示,由图可知,加载页面仅需要用时2ms。

    在查询结果页面,查询一个关键词“IOS”,页面的等待时间为223ms。同样由于缓存的作用,刷新的等待时长变为8ms。结果如图4.14与4.15所示。

    4.6 小结本章介绍了整个系统的每个模块的整体设计、详细设计以及实现方式。并进行了简单的分析以及性能的调优,最终系统运行性能良好,稳定,系统结构简洁,功能完善。
    结 论随着计算机网络技术的发展,越来越多的电子商务网站、图书分析网站以及一些图书馆系统都采用了图书推荐系统这种智能化的解决方案,它有效的实践了长尾理论,增加了用户粘性。
    本文对基于Web的图书推荐系统的展示平台做了初步研究,开发了一个平台,开发过程包括网站数据库设计、数据清洗、系统结构设计、系统搭建、版本控制、性能调优等,功能模块包括图书展示、图书搜索、图书推荐、用户登录注册等,还对设计的过程、网站的结构、项目的管理方法进行了探索,对项目中遇到的难点给出了一些解决方案,起到的效果也比较不错。
    研究的核心部分是系统的设计与工具的使用。由于系统开发采用MVC结构,系统各个层级与模块之间的关系以及数据库结构,都需要一个具有高扩展性和可复用的设计,否则在开发后期或是在后期维护阶段,都会因为工程大小的增长而带来耦合性的急剧上升,最终导致功能实现与错误修改变得异常艰难。一些开源的开发框架与项目管理工具在系统开发过程中会极大地减轻开发者浪费在无关事务上的工作量,开发者不需要去操心布局怎样实现才能美观,怎样达到浏览器兼容,以及怎样去管理版本,控制查看进度,去哪里下载依赖包,项目需要哪些依赖,依赖包怎样升级等等。工具也可以减少开发者的重复劳动,比如MyBatis Generator可以自动生成单表操作的Mapper文件。
    开发中的难点是数据的清洗过程与数据库查询效率的优化。数据的清洗过程需要读取出记录后,对数据解析、去新表查询,根据查询结果插入到新表或者更新新表。操作比较复杂,由于数据的格式极不规整,在解析时,花了许多时间研究数据的格式的可能性,编写代码去除干扰数据,并用正则表达式解析它。一开始数据查询效率极低,在分析了SQL查询语句之后,对数据相应字段创建了索引,才使得数据查询的速度符合期望,解决了查询的效率问题。
    参考文献[1]项亮,陈义,王益著.推荐系统实践[M].北京:人民邮电出版社,2012.
    [2]Vincent, L.Google Book Search: DocumentUnderstanding on a Massive Scale[J].Document Analysis and Recognition,2007: 819 - 823
    [3]裴玉洁著.采用数据挖掘技术的自动化推荐系统的研究[D].2012.
    [4]吴定勇,王峰著.亚马逊书店的网络售书之道[J].北京:当代传播,2008, (6): 123-125.
    [5]李连焕,刘建东著.基于Web日志挖掘的图书借阅推荐算法研究[J].北京:硅谷,2012,(6): 93-94.
    [6]古丽拜天.卡米尔著.基于Web数据挖掘的智能推荐研究[D].湖南:中南大学,2010.
    [7]李俊杰著.Maven在企业Java软件产品中的应用[D].北京:北京邮电大学,2011.
    [8]刘悦之著.基于Git的分布式版本控制系统的设计与实现[D].上海:同济大学,2012.
    [9]张琛,吴跃,邱会中著.基于Structs+Spring+Hibernate的整合架构及其在电信业中的应用[D].四川成都:电子科技大学,2006.
    [10]陆荣幸,郁洲,阮永良,王志强著.J2EE平台上MVC设计模式的研究与实现[J].计算机应用研究,2003, 20(3): 144-146.
    [11]Praveen Gupta, Prof. M.C. Govil. MVC DesignPattern for the multi framework distributed applications using XML, spring andstruts framework[J].International Journal on Computer Science and Engineering,2010, 2(4):1047-1051.
    [12]冯润民著.基于SSH的高校学生管理系统设计与实现[J].北京:计算机工程,2009, 35(6): 280-282.
    [13]DaveMinter,Jeff Linwood著.Hibernate基础教程[M].陈剑瓯等译.北京:人民邮电出版社,2008.
    [14]孙强,孙龙清,邱小彬著.基于Structs+Spring+iBATIS的轻量级Web应用框架研究[J].北京:计算机应用与软件,2008, 25(10): 135-137.
    [15]王庆民著.基于Web图书推荐系统设计[J].山西:晋图学刊,2011, (1): 35-37.
    [16]曾庆辉,邱玉辉著.一种基于协作过滤的电子图书推荐系统[J].北京:计算机科学,2005,32(6): 147-150.
    [17]张富国著.电子商务协同过滤推荐系统的研究与进展[D].江西:江西财经大学信息管理学院,2010.
    [18]田元,宋纬华,李婷婷著.基于Markov链的图书推荐系统的研究与设计[J].陕西:西安理工大学图书馆,2012, 32(6): 79-82.
    致 谢本课题是在指导老师的亲切关怀和悉心指导下完成的。老师对提交文档的细心浏览,总能挑出不合要求的地方,指导我改正。对我的毕设进度也一直很关心,提醒我按时提交。老师给予我很大帮助,在项目初期确定题目给我很大帮助。在此,谨向老师们表示由衷的感谢。
    另外,要感谢学姐、同学以及学弟,在项目初期,与老师一起讨论我的题目与内容,在整个毕设过程中,学姐对我提交的文档、论文以及整个毕设中一些注意事项为我提供指导意见。感谢同学,在我上班时间帮我打印、找老师签字以及提交文档,还有对我关于毕业一些疑问的解答。
    最后,感谢我使用的所有开源、自由软件的开发者们。你们无私的工作引导了各个领域的技术创新,改变了人们的生活方式。没有前人的工作,我不可能完成这个课题,感谢你们,感谢所有关心、帮助过本课题的老师、同学们!
    8 评论 32 下载 2018-09-29 21:17:50 下载需要17点积分
  • 基于Python和SQL SERVER数据库实现的实验信息综合管理系统

    1 需求分析1.1 需求来源本系统是对于实验综合信息进行管理的系统。
    1.1.1 功能需求
    学生用户:修改系统登陆密码、查询选择的课程、查询选择课程对应的实验课表、导出查询到的课程数据、实验课表数据
    教师用户:修改系统登陆密码、修改个人联系方式、查询教授的课程、查询实验安排、增加课程信息、增加实验信息、按学号查询出勤率、按班级查询出勤率、导出查询到的数据记录
    管理员用户:增加学生信息、修改学生信息、增加教师信息、修改教师信息、增加课程信息、修改课程信息,增加实验信息、修改实验信息,增加实验室信息、修改实验室信息,更新实验打卡信息

    1.1.2 数据需求系统涉及的数据表为8个表,管理员信息表(ADMINB)、教师表(JSB)、教师课程表(JSKCB)、课程表(KCB)、实验打卡表(SYDKB)、实验室表(SYSB)、实验信息表(SYXXB)、学生表(XSB)。
    其中管理员信息表包含的基本属性为管理员用户名、登陆密码。教师表包含的几本书新为教师姓名、联系方式、登陆密码。教师课程表包含的基本属性为教师姓名、课程代码。课程表包含的基本属性为课程代码、课程名称、开课学院。实验打卡表包含的基本属性为实验编号、学生学号、打卡状态。实验室表包含的基本属性为实验中心、实验分室、上课地点。实验信息表包含的基本属性为实验编号、课程代码、实验项目、上课老师、辅助教师、上课日期、星期几、上课地点。学生表包含的基本属性为学号、姓名、班级、登陆密码。
    1.1.3 性能需求
    要求系统在本地访问时数据具有可靠性、运行速度快,简单快捷
    要求系统能在多系统(Windows、Linux)、多终端(PC、手机)

    1.2 设计目的利用服务器和大容量存储等最新的硬件设备,以及数据库和网络技术所开发出的实验信息管理系统使用户能对大量的实验综合信息进行高效的管理。通过实验信息管理系统,信息录入、查询等原先繁复枯燥的工作的效率得到了显著的提升;更重要的是数据的准确性和安全性也同时得到了保证。
    1.3 设计要求不同类型的用户能够通过本系统使用不同的功能,对实验综合信息进行管理。
    1.4 开发工具及相关技术通过采用Microsoft SQL Server等大型关系型数据库,实验的各项数据的存储更为规范和完整。数据库技术也使得实验数据的备份和恢复变得简单便捷。而在硬件方面,采取RAID5等存储解决方案组成的磁盘阵列,以极低的存储成本极大的提高了数据的安全性。通过采用python平台开发,采用了基于B/S的三层开发结构(UI、BLL、DAL)。同时运用tkinter等GUI的最新技术,提供美观实用的系统界面和顺畅的用户体验。
    2 总体设计2.1 总体结构设计为了满足实验综合信息管理系统各方面信息的管理功能,需要明确用户的各个功能。数据库中应该有教师、学生以及管理员的基本信息,这些来自于本系统。教师、学生只能在客户端进行操作。管理员可以使用管理端操作,修改、删除、增加教师、学生的信息。此类权限不对教师、学生用户开放。除此之外,还要通过登录名和密码严格限定登陆者的身份并且控制相应的操作权限,只有管理员有权限修改账户以及密码,防止其他密码泄露造成的其他问题。
    2.1.1 模块设计系统模块设计:

    2.2.2 模块功能描述“管理员用户”子系统针对管理员用户的日常工作流程,分为“学生管理”,“教师管理”,“课程管理”,“实验管理”,“实验室管理”等子模块。其模块具体功能描述如下:

    “学生管理”模块:该模块对学生信息进行维护,如对增加新入学学生的信息,为转专业学生更改班级信息,为改名学生更改姓名信息,删除退学、毕业学生信息,为忘记登陆密码的学生用户重新设置用户密码等
    “教师管理”模块:该模块对教师信息进行维护,如对增加新入职老师的信息,删除离职老师的信息,为忘记登陆密码的教师用户重新设置登陆密码等
    “课程管理”模块:该模块对课程信息进行维护,如对课程名称、开课学院等信息系的修改,增加新开设课程的信息
    “实验管理”模块:该模块对实验信息进行维护,如对实验下属各种信息的修改调整、增加新的实验信息、更新实验打卡信息等
    “实验室管理”模块:该模块对实验室信息进行维护,如对实验中心、实验分室、实验教师信息的修改、增加等

    “教师用户”子系统针对教师用户的日常使用流程,分为“学生管理”模块,“课程管理”模块,“实验管理”模块、“个人管理”模块。其模块具体功能描述如下:

    “学生管理”模块:该模块对学生信息进行查询,如对班级整体实验打卡记录的查询,对指定具体学号的学生实验打卡记录的查询,对查询到的信息进行导出等
    “课程管理”模块:该模块对课程信息进行查询与维护,如将新教授的课程信息录入系统,查询正在教授的课程信息等
    “实验管理”模块:该模块对实验信息进行查询与维护,如查询已安排为授课教师的实验信息,添加新的实验信息等
    “个人管理”模块:该模块对教师个人信息进行维护,如修改自己的登陆密码、修改个人的联系方式等

    “学生用户”子系统针对学生用户的日常使用流程,分为“个人管理”模块,“课程管理”模块,“实验管理”模块等。其模块具体功能描述如下:

    “个人管理”模块:该模块对学生用户个人信息进行维护,如修改自己的登陆密码等
    “课程管理”模块:该模块对课程信息进行查询,如查询自己选择的课程信息等
    “实验管理”模块:该模块对实验信息进行查询,如查询自己选择的课程所安排的实验信息等

    2.2 数据库设计2.2.1 概念结构设计
    管理员信息表(ADMINB)主要用于在系统中储存管理员信息,包含了管理员的登陆ID、密码,其中登陆ID是主键
    教师表(JSB)主要用于在系统中储存教师信息,包含了教师用户的姓名、密码、联系方式,其中姓名是主键。管理员用户可对该表进行维护
    教师课程表(JSKCB)主要用于在系统中储存教师授课信息,包含了教师姓名、课程代码。管理员用户、教师用户可对该表进行维护
    课程表(KCB)主要用于储存课程信息,包含了课程代码、开课学院、课程名称,其中课程代码是主键。管理员用户可对该表进行维护
    实验打卡表(SYDKB)主要用于储存学生打卡信息,包含了实验编号,学生学号、打卡状态。管理员用户可对该表进行维护,教师用户可对该表进行查询
    实验室表(SYSB)主要用于储存实验室信息,包含了实验中心、实验分室、实验教室,其中实验教室是主键。管理员用户可对该表进行维护
    实验信息表(SYXXB)主要用于储存实验信息,包含了实验编号、课程代码、实验项目、上课老师、辅助教师、上课日期、星期几、上课地点,其中实验编号是主键。管理员用户、教师用户可对该表进行维护
    学生表(XSB)主要用于储存学生信息,包含了学号、姓名、班级、登陆密码,其中学号是主键。管理员用户可对该表进行维护

    系统E-R图

    2.2.2 逻辑结构设计根据E-R模型转换关系结构:

    教师实体集(JSB)可以转换化为关系:
    教师用户(姓名,联系方式,登陆密码)

    管理员实体集(ADMINB)可以转化为关系:
    管理员用户(登陆ID,密码)

    课程实体集(KCB)可以转化为关系:
    课程(课程代码,开课学院,课程名称)

    实验室实体集(SYSB)可以转化为关系:
    实验室(实验分室、实验中心、实验教室)

    实验信息实体集(SYXXB)可以转化为关系:
    实验信息(实验编号、课程代码、实验项目、上课老师、辅助教师、上课日期、星期几)

    学生实体集(XSB)可以转换为关系:
    学生(姓名、学号、班级)

    教师与课程之间是n:m的联系,可以转化为一个关系:
    教师课程(JSKCB)(教师姓名、课程编号)

    学生与实验打卡信息之间是1:m的联系,可以对学生关系进行拓展,但由于打卡信息较多,进行扩展会导致数据库冗余信息较多,故单独转化为一个关系:
    学生打卡(XSDKB)(学号、实验编号、打卡信息)

    实验室与实验信息之间是n:m的联系,可以对实验信息进行拓展,不必单独转为一个关系:
    实验信息(实验编号、课程代码、实验项目、上课老师、辅助教师、上课日期、星期几、实验教室)
    2.2.3 物理结构设计教师表(JSB)



    字段名称
    数据类型
    字段长度
    是否为空
    备注




    姓名
    varchar
    20
    No
    主码


    联系电话
    varchar
    20
    Yes



    登陆密码
    varchar
    255
    Yes



    教师课程表(JSKCB)



    字段名称
    数据类型
    字段长度
    是否为空
    备注




    课程代码
    varchar
    20
    No



    上课教师
    varchar
    20
    No



    课程表(KCB)



    字段名称
    数据类型
    字段长度
    是否为空
    备注




    课程代码
    varchar
    20
    No
    主码


    开课学院
    varchar
    20
    No



    课程名称
    varchar
    255
    No



    **实验打卡表(SYDKB)



    字段名称
    数据类型
    字段长度
    是否为空
    备注




    实验编号
    varchar
    20
    No



    学号
    varchar
    20
    No



    实际上课时间
    varchar
    255
    No



    状态
    varchar
    255
    No



    实验室表(SYDKB)



    字段名称
    数据类型
    字段长度
    是否为空
    备注




    实验中心
    varchar
    255
    No



    实验分室
    varchar
    255
    No



    实验教室
    varchar
    255
    No
    主码



    实验信息表(SYXXB)



    字段名称
    数据类型
    字段长度
    是否为空
    备注




    实验编号
    varchar
    255
    No
    主码


    课程代码
    varchar
    255
    No



    实验项目
    varchar
    255
    No



    上课老师
    varchar
    255
    No



    辅助教师
    varchar
    255
    Yes



    上课日期
    varchar
    255
    No



    星期几
    varchar
    255
    No



    实验教室
    varchar
    255
    No



    学生表(XSB)



    字段名称
    数据类型
    字段长度
    是否为空
    备注




    姓名
    varchar
    20
    No



    学号
    char
    11
    No
    主码


    班级
    varchar
    255
    No



    登陆密码
    varchar
    255
    Yes



    管理员表(XSB)



    字段名称
    数据类型
    字段长度
    是否为空
    备注




    登陆ID
    varchar
    20
    No
    主码


    登陆密码
    varchar
    255
    No



    3 详细设计3.1 数据库的创建create database SYDK
    3.2 表的创建import pyodbccnxn = pyodbc.connect('DRIVER={SQL Server};SERVER=127.0.0.1;DATABASE=SYDK;UID=sa;PWD=test')cursor = cnxn.cursor()cursor.execute(""" Create Table XSB ( 班级 varchar(20), 学号 char(11), 姓名 varchar(20), 登陆密码 varchar(255) ) """)#学生表cursor.execute(""" Create Table KCB ( 课程代码 varchar(20), 课程名称 varchar(255), 开课学院 varchar(20) ) """)#课程表cursor.execute(""" Create Table JSB ( 姓名 varchar(20), 联系电话 varchar(20), 登陆密码 varchar(255) ) """)#教师表cursor.execute(""" Create Table SYXXB ( 实验编号 varchar(20), 课程代码 varchar(20), 实验项目 varchar(20), 上课老师 varchar(20), 辅助教师 varchar(20), 上课日期 varchar(255), 星期几 varchar(20), 实验中心 varchar(255), 实验分室 varchar(255), 上课地点 varchar(255) ) """)#实验信息表cursor.execute(""" Create Table SYDKB ( 实验编号 varchar(20), 学号 varchar(20), 实际上课时间 varchar(255), 状态 varchar(255) ) """)#实验打卡表cursor.execute(""" Create Table SYSB ( 实验中心 varchar(255), 实验分室 varchar(255), 上课地点 varchar(255) ) """)#实验室表cursor.execute(""" Create Table JSKCB ( 课程代码 varchar(20), 上课老师 varchar(20) ) """)#教师课程表cursor.execute(""" Create Table ADMINB ( id varchar(20), password varchar(20) ) """)#管理员表cursor.commit()cursor.close()
    3.3 数据设定本系统对接南京信息工程大学实践教学综合管理平台,通过访问平台接口,以正则表达式的方法,获取得到教师信息、学生信息、实验信息,从其内含的依赖关系衍生出实验打卡表,实验室表,课程表,教室课程表。同时,管理员用户通过本系统,也可调用该程式对数据库中信息进行维护工作。核心代码如下:
    cnxn = pyodbc.connect('DRIVER={SQL Server};SERVER=127.0.0.1;DATABASE=SYDK;UID=sa;PWD=test') cursor = cnxn.cursor() cursor.execute("SELECT * FROM SYXXB WHERE 实验编号="+str(i)) row = cursor.fetchall() print(i) if (len(row)!=0): cursor.close() continue url="http://sjjx.nuist.edu.cn/syjx/admin/experiment/looksyzx.aspx?type=kb&id="+str(i) try: req=urllib.request.Request(url) resp=urllib.request.urlopen(req) except: print("oppops") continue data=resp.read().decode('GBK') w1='<td width=\'610\' height=\'25\' style=\'padding-left:5px;\'>' w2='</td></tr>' pat=re.compile(w1+'(.*?)'+w2,re.S) sybz=pat.findall(data) if(len(sybz)==0): continue del(sybz[9]) w1='<td height=\'25\' style=\'padding-left:5px; width: 82px;\'>' w2='</td>' pat2=re.compile(w1+'(.*?)'+w2,re.S) syb=pat2.findall(data) del(syb[9]) for j in range(0,len(syb)): print (syb[j]+':'+sybz[j]) if(sybz[4].find("(")!=-1): tmp=sybz[4] tmp=tmp[0:tmp.find("(")] sybz[4]=tmp print(sybz[4]) print(sybz[5]) w1='<td align=\'center\'>' w2='</td>' pat3=re.compile(w1+'(.*?)'+w2,re.S) xsbz=pat3.findall(data) cursor.execute("SELECT * FROM JSB WHERE 姓名=\'"+sybz[4]+"\'") row = cursor.fetchall() if (len(row)==0): cursor.execute("insert into JSB values ("+"\'"+sybz[4]+"\'"+",\'"+sybz[5]+"\'"+",\'\')") cursor.execute("SELECT * FROM KCB WHERE 课程代码="+sybz[0])cursor.commit();cursor.close();
    3.4 模块设定3.4.1 登陆模块本模块供不同类型的用户登陆使用,通过单选框的选择,可以选择登陆的用户类型,输入账号密码后,登陆本系统。系统访问数据库判断密码情况,若密码错误则无法成功登陆,访问请求被系统拒绝,若密码正确则进入对应的综合管理模块。

    其核心代码如下:
    from tkinter import *from CoCenter import *import tkinter.messageboxclass LoginFrame (Frame): def __init__(self,master): def ButtonClick(self): s1 = self.ent1.get() s2 = self.ent2.get() if(self.v.get() == 0): self.login(s1,s2,"XSB","学号") elif(self.v.get() == 1): self.login(s1,s2,"JSB","姓名") elif(self.v.get() == 2): self.login(s1,s2,"admin") else: showinfo(title = '错误', message = '未选择登陆方式!') def login(self,name,pwd,tbname="XSB",ab="学号"): if(tbname=="admin"): import pyodbc cnxn = pyodbc.connect('DRIVER={SQL Server};SERVER=127.0.0.1;DATABASE=SYDK;UID=sa;PWD=test') cursor = cnxn.cursor() cursor.execute("SELECT password FROM ADMINB WHERE id =\'" +name+"\'") row = cursor.fetchall() cursor.close() if(len(row)==0): tkinter.messagebox.showinfo(title = '错误', message = '用户名不存在') else: if(getVal(str(row[0]))==pwd): tkinter.messagebox.showinfo(title = '成功', message = '登陆成功')
    3.4.2 综合管理模块学生用户管理模块
    学生用户登陆成功后,则可以进入学生用户管理模块,通过点击学生用户管理模块上的按钮,可以调用相应的信息维护模块。

    其核心代码如下:
    self.button=tk.Button(frame,text = "更改登陆密码",pady=10,command = self.XSinfoUpdate)self.button.grid(row = 0,column = 0,padx=10)self.button1=tk.Button(frame,text = "查询个人选课",pady=10,command = self.XSCrouseQry)self.button1.grid(row = 0,column = 1,padx=10,pady=15)self.button2=tk.Button(frame,text = "查询实验课表",pady=10,command = self.XSCrouseQry2)self.button2.grid(row = 0,column = 2,padx=10)self.geometry('310x80')
    教师用户管理模块
    教师用户登陆成功后,则可以进入教师用户管理模块,通过点击教师用户管理模块上的按钮,可以调用相应的信息维护模块。

    其核心代码如下:
    self.button=tk.Button(frame, text = "更改个人信息",pady=10,command = self.JSinfoUpdate)self.button.grid(row = 0,column = 0,padx=10,pady=10)self.button1=tk.Button(frame,text = "更改登陆密码",pady=10,command = self.JSPwdUpdate)self.button1.grid(row = 0,column = 1)self.button2=tk.Button(frame,text = "查询所教课程",pady=10,command = self.JSCrouseQry)self.button2.grid(row = 1,column = 0,padx=10,pady=10)self.button3=tk.Button(frame,text = "查询实验信息",pady=10,command = self.JSExpQry)self.button3.grid(row = 1,column = 1)
    管理员用户管理模块
    管理员用户登陆成功后,则可以进入管理员用户管理模块,通过点击管理员用户管理模块上的按钮,可以调用相应的信息维护模块。

    其核心代码如下:
    self.button=tk.Button(frame,text = "添加学生信息",pady=10,command = self.AdStuAdd)self.button.grid(row = 0,column = 0,padx=10,pady=10)self.button1=tk.Button(frame,text = "修改学生信息",pady=10,command = self.AdStuUpdate)self.button1.grid(row = 0,column = 1,padx=10,pady=10)self.button2=tk.Button(frame,text = "添加教师信息",pady=10,command = self.AdJSAdd)self.button2.grid(row = 1,column = 0,padx=10,pady=10)self.button3=tk.Button(frame,text = "修改教师信息",pady=10,command = self.AdJSUpdate)self.button3.grid(row = 1,column = 1,padx=10,pady=10)self.button4=tk.Button(frame,text = "添加课程信息",pady=10,command = self.AdKCAdd)self.button4.grid(row = 2,column = 0,padx=10,pady=10)self.button5=tk.Button(frame,text = "修改课程信息",pady=10,command = self.AdKCUpdate)self.button5.grid(row = 2,column = 1,padx=10,pady=10)self.button6=tk.Button(frame,text = "添加实验信息",pady=10,command = self.AdExpAdd)self.button6.grid(row = 3,column = 0,padx=10,pady=10)self.button6=tk.Button(frame,text = "修改实验信息",pady=10,command = self.AdExpUpdate)self.button6.grid(row = 3,column = 1,padx=10,pady=10)self.button6=tk.Button(frame,text = "添加实验室信息",pady=10,command = self.AdExpPlaceAdd)self.button6.grid(row = 4,column = 0,padx=10,pady=10)
    3.4.3 信息维护模块用户信息维护模块
    根据不同用户,调用不同的信息维护模块,具体描述如下:
    学生用户调用本模块对登陆密码进行维护

    其核心代码如下:
    Sql("EXEC XS_INFO_UPDATE \'"+self.name+"\',\'"+str(self.newp.get())+"\'",2)tk.messagebox.showinfo('提示','修改成功')
    教师用户调用本模块对登陆密码、联系方式进行维护。

    其核心代码如下:
    Sql("EXEC JS_PWD_UPDATE \'"+self.name+"\',\'"+str(self.newp.get())+"\'",2)tk.messagebox.showinfo('提示','修改成功')
    管理员用户调用本模块对学生的所有属性、教师用户的所有属性进行维护。

    其核心代码如下:
    if(self.element=="STUDENT"): if(self.flag=="ADD"): Sql("INSERT INTO XSB VALUES (\'"+str(self.clsname.get())+"\',\'"+str(self.id.get())+"\',\'"+str(self.name.get())+"\',\'"+str(self.pwd.get())+"\')",2) else: Sql("UPDATE XSB SET 班级=\'"+str(self.clsname.get())+"\',姓名=\'"+str(self.name.get())+ "\',登陆密码=\'"+str(self.pwd.get())+"\' \n WHERE 学号=\'"+str(self.id.get())+"\'",2)if(self.element=="TEACHER"): if(self.flag=="ADD"): Sql("INSERT INTO JSB VALUES (\'"+str(self.id.get())+"\',\'"+str(self.tel.get())+"\',\'"+str(self.pwd.get())+"\')",2) else: Sql("UPDATE JSB SET 联系电话=\'"+str(self.tel.get())+ "\',登陆密码=\'"+str(self.pwd.get())+"\' \n WHERE 姓名=\'"+str(self.id.get())+"\'",2)
    课程信息维护模块
    不同用户对于课程信息维护模块具有不同的权限,具体如下:
    学生用户具有对课程信息的查询权限,对于查询到的课程信息,学生用户可以选择是否导出为excel表格形式:

    其核心代码如下:
    row=Sql("EXEC XS_Course_Qry1 \'"+ self.stuid+"\'",1)table=PrettyTable(["课程代码","课程名称","开课学院"])row1=getVal(row)for i in range(0,int(len(row1)/3)):table.add_row([row1[i*3],row1[i*3+1],row1[i*3+2]])tk.messagebox.showinfo(title = '课程', message = table)a=tk.messagebox.askokcancel('提示', '是否导出选课表')
    教师用户具有对课程信息的查询、创建权限,对于查询到的课程信息,教师用户可以选择是否导出为excel表格形式:

    核心代码如下:
    row=Sql("EXEC JS_Course_Qry \'"+ self.stuid+"\'",1)table=PrettyTable(["课程代码","课程名称","开课学院"])row1=getVal(row)for i in range(0,int(len(row1)/3)):table.add_row([row1[i*3],row1[i*3+1],row1[i*3+2]])tk.messagebox.showinfo(title = '课程', message = table)
    管理用户具有对课程信息的所有权限:

    其核心代码如下:
    if(self.element=="CROUSE"): if(self.flag=="ADD"): Sql("INSERT INTO KCB VALUES (\'"+str(self.id.get())+"\',\'"+str(self.name.get())+"\',\'"+str(self.inst.get())+"\')",2) elif(self.flag=="UPDATE"): Sql("UPDATE KCB SET 开课学院=\'"+str(self.inst.get())+ "\',课程名称=\'"+str(self.name.get())+"\' \n WHERE 课程代码=\'"+str(self.id.get())+"\'",2) else: Sql("INSERT INTO KCB VALUES (\'"+str(self.id.get())+"\',\'"+str(self.name.get())+"\',\'"+str(self.inst.get())+"\')",2) tmp=str(self.flag).split("2") Sql("INSERT INTO JSKCB VALUES (\'"+str(self.id.get())+"\',\'"+str(tmp[1])+"\')",2)
    实验信息维护模块
    根据不同用户,调用不同的信息维护模块,具体描述如下:
    学生用户对于实验信息具有查询权限,对于查询到的实验信息,可以选择是否导出为EXCEL表格

    其核心代码如下:
    row =Sql("EXEC XS_Course_Qry2 \'"+ self.stuid+"\'",1) table=PrettyTable(["实验编号","课程代码","实验项目", "上课老师","辅助教师","上课日期","星期几", "实验中心","实验分室","上课地点"]) row1=getVal(row) for i in range(0,int(len(row1)/10)): table.add_row([row1[i*10],row1[i*10+1],row1[i*10+2], row1[i*10+3],row1[i*10+4],row1[i*10+5], row1[i*10+6],row1[i*10+7],row1[i*10+8],row1[i*10+9]]) tk.messagebox.showinfo(title = '课表', message = table) a=tk.messagebox.askokcancel('提示', '是否导出课表')
    教师用户对于实验信息具有查询、修改权限,对于查询到的实验信息,可以导出为EXCEL表格

    其核心代码如下:
    if(self.element=="EXP"): if(self.flag=="ADD"): Sql("INSERT INTO SYXXB VALUES (\'"+str(self.id.get())+"\',\'"+str(self.kcdm.get())+"\',\'"+str(self.syxm.get())+"\',\'"+str(self.skls.get())+"\',\'" +str(self.fzjs.get())+"\',\'"+str(self.skrq.get())+"\',\'"+str(self.xqj.get())+"\',\'"+str(self.syzx.get())+"\',\'"+str(self.syfs.get())+"\',\'"+str(self.skdd.get())+"\')",2) tk.messagebox.showinfo('提示','更新成功!')
    同时,教师用户对于实验打卡信息具有查询权限,可以通过学生学号查询打卡信息,也可以通过所教班级查询打卡信息,并将打卡信息导出到EXCEL表格。

    其核心代码如下:
    for i in range(0,len(row1)): if(len(row1[i])==11): row=Sql("EXEC JS_STU_QRY5 \'"+self.name+"\',\'"+row1[i]+"\'",1) if(len(row)==0): continue else: row=Sql(""" declare @f1 float declare @f2 float EXEC JS_STU_Qry2 \'"""+str(row1[i])+"\',\'"+self.name+"""\',@f1 output,@f2 output SELECT @f1,@f2""",1) row2=getVal(row) table.add_row([str(row1[i]),str(row2[0]),str(row2[1])]) tk.messagebox.showinfo(title = '查询结果', message =table)
    管理员用户对于实验信息具有全部权限

    核心代码如下:
    Sql("UPDATE SYXXB SET 课程代码=\'"+str(self.kcdm.get())+"\',实验项目=\'" +str(self.syxm.get())+"\',上课老师=\'" +str(self.skls.get())+"\',辅助教师=\'" +str(self.fzjs.get())+"\',上课日期=\'" +str(self.skrq.get())+"\',星期几=\'" +str(self.xqj.get())+"\',实验中心=\'" +str(self.syzx.get())+"\',实验分室=\'" +str(self.syfs.get())+"\',上课地点=\'" +str(self.skdd.get()) +"\' \n WHERE 实验编号=\'"+str(self.id.get())+"\'",2)
    实验室信息维护模块
    管理员用户对实验室信息具有全部权限

    其核心代码如下:
    if(self.element=="EXPPLACE"): if(self.flag=="ADD"): Sql("INSERT INTO SYSB VALUES (\'"+str(self.syzx.get())+"\',\'"+str(self.syfs.get())+"\',\'"+str(self.skdd.get())+"\')",2) else: Sql("UPDATE SYSB SET 实验中心=\'"+str(self.syzx.get())+ "\',实验分室=\'"+str(self.syfs.get())+"\' \n WHERE 上课地点=\'"+str(self.skdd.get())+"\'",2)
    4 总结整个系统的设计过程对于我来说算是个学习、探索的过程,通过实践和对比别人开发程序的过程。在整个设计过程中,出现过很多的问题,很多繁琐的东西都需要反复的修改,主要是前期工作不彻底,对系统的需求分析的要求认识不够清楚,使得在后边的工作中不得不经常反复去修改。使我体会到设置中每一步的重要性。所以在分析一个问题时,我们需要站在一个有远见的高度。
    虽然时间紧迫但我学会了很多,也感到自身知识的贫乏,希望在日后的努力中能做出更完善的系统。
    2 评论 17 下载 2018-10-24 21:35:32 下载需要11点积分
  • 基于JAVA FX的葫芦娃游戏

    一、总体描述1.1 战斗方式相遇之时一决雌雄!
    设计的战斗方式为近战,只有双方阵营的生物处在相邻的单元格时,才会触发战斗。

    与图中红色格子相邻的只有绿色格子,灰色格子与红色格子不相邻。
    1.2 战斗空间在一个15×15的二维矩阵格子上,葫芦娃与妖精双方排好队形准备战斗。

    葫芦娃总是在空间左侧排好阵型,妖精总是在空间右侧排好阵型。葫芦娃可供选择的阵有四个,分别是:长蛇阵、鹤翼阵、雁行阵、冲轭阵(其他四种阵型因为人数限制葫芦娃一方无法摆出),妖精一方可以排出全部八种阵型。在战斗开始之前的任何时刻,玩家都可选择更换双方阵型。
    当双方排好阵后,点击开始按钮即展开战斗(或者直接按下空格键,程序会调用默认阵型并展开战斗)。
    1.3 多线程部分每个生物体都是一个线程,多个线程同时运行,共享地图信息。当任何一个线程对共享信息进行操作时,共享数据会在此时被锁定,以保证线程安全,同时也避免了多个生物体跑到同一个单元格、多个生物体杀死一个生物体的问题。
    1.4 判断获胜方判断获胜方有两个层面的意思:

    第一个层面是大局上的胜利,是妖精一方获胜还是葫芦娃一方得胜
    第二个层面是局部的胜利,当两个阵营中的两个个体相遇,一决生死时,用什么决定哪一个存活

    解决第一个问题所采用的办法是在三十秒内,若战斗结束,则仍有生物存活一方获胜;第二个问题的解决办法是以一个根据生物体战斗力所计算出的获胜概率,随机确定获胜者。
    1.5 回放将战斗中的数据信息保存为一个.txt文件,点击回放按钮后,读取该文件进行回放。因为是以字符的方式保存生物体位置信息,所以每次战斗所产生的文件不会超过200KB。
    二、细节阐述2.1 JavaFX采用scene builder来设计JavaFX界面,拖拽操作的方式降低了自己编写fxml文件的难度。
    2.2 maven整个工程以maven组织,运行起来比较简单。
    2.3 模块说明Main包
    包括两个类,主函数Main类和MainControl类,用于和用户交互。
    MainControl中设置了十四个按钮,四个葫芦娃阵型按钮,八个妖精阵型按钮,一个开始按钮,一个复盘按钮。
    Formation包
    包括九个类,一个Formation基类和八个阵型子类。继承关系如下图所示:

    在阵型子类中让生物体“复苏”。
    create包
    包括七个类,一个Creature基类和六个生物体子类,另外还包含两个葫芦娃的枚举类型。

    Creature基类中有以下几种对生物体的基础描述
    public int power; //力量public int speed; //速度public int agility; //敏捷public int X; //x轴坐标public int Y; //y轴坐标public boolean justorevil; //所属阵营public String name; //名称
    每一个生物体都有这七个基础属性,前三个属性用来描述生物体的战斗力。因为葫芦娃的特殊性,所以用枚举类型CalabashName和Color来描述他们各自的名称和颜色信息
    Classification包
    只有一个类,Classification类,用来描述生物体所属阵营。
    InitImage包
    只有一个InitImage类,用来把图片加载到GUI界面上。
    Thread包
    包含三个类,一个主线程类MainThread,一个子线程类MyThread,以及一个文件类。主线程类在开启战斗时创建,子线程类由主线程类创建,每一个子线程类对应一个生物体对象。文件类在复盘的时候创建。
    2.5 二维空间的产生每个方格都是拖拽在JavaFX界面上的矩形,组成了一个15×15的二维矩阵,在这个二维矩阵上覆盖了JavaFX中的canvas控件,用以获得二维矩阵的像素点坐标。
    2.6 运行流程有两种方式开启战斗,一种是当程序运行起来后,直接按下空格键选择默认阵型进行战斗,另一种是玩家自主选择合适的阵型,然后点击开始按钮进行战斗。
    摆阵

    2.6.1 自主择阵的方式下当玩家为葫芦娃选择一种阵型后,程序将进入阵型类执行,在具体的阵型中,会让葫芦娃“活过来”,并将他们安排在二维矩阵中每个个体应当处于的位置。位置信息用一个全局的地图表存储。该地图表为
    List<Creature> fieldlist
    地图表大小为225,每个元素代表二维矩阵中的一个格子上站立的生物体。该地图表初始化为“Empty”生物体,代表所有位置都没有生物站立。在具体的阵型中,当为葫芦娃或妖精排出阵型后,地图表对应位置原本的“Empty”类型生物体将被葫芦娃或妖精替换。
    当双方都排好阵型后,点击开始按钮,程序创建一个主线程,这个主线程执行两部分操作:第一部分是刷新战斗界面显示并将战斗画面记录下来,第二部分是为每一个生物体创建一个线程。这样会避免在每个生物体的线程中直接刷新界面造成线程不安全的问题。在每个生物体的线程中,他们将调用各自的move方法进行移动,倘若检测到自己的邻居有对手,双方将会一决雌雄。
    //在主线程的run方法里为每个生物体对象创建线程for(int i=0;i<fieldlist.size();i++){ if(fieldlist.get(i).name!="empty") { allThread.add(new Thread(new MyThread(mainCanvas,fieldlist.get(i),new ReentrantLock(),fieldlist))); }}for (Thread t:allThread) { t.start();}
    //在主线程的run方法里刷新界面并记录界面数据InitImage.setImage(mainCanvas.getGraphicsContext2D(), fieldlist);writeFile();
    胜负的判定来源于两个随机数,这两个随机数的取值范围上限是战斗双方战斗力的值。战斗力被描述为三种属性:力量、敏捷、速度。对于不同的生物体,这三种属性的初始值不同,并且战斗力并非是这三种属性的简单叠加,生成战斗力的计算方式为:战斗力 = a*力量 + b*敏捷+c*速度。a,b,c的取值与每个生物体的能力侧重有关,比如对于葫芦娃中的大娃来说,他力量初始值比别的要高一些,同时在战斗中,他的战力更多来源于力量,所以a的取值也会相对较高。生成两个随机数后,将会选取随即数大的一方作为获胜方,失败的一方将会变成“尸体”留在原地,同时作为路障拦在那里。
    程序中同时也设置了随机死亡与消失机制——生物体掉入对方之前设下的陷阱中,将有可能死亡并留下“尸体”,也有可能直接消失在地图中。而且,已经成为“尸体”的生物体也有可能消失在地图中,以此来保证战斗的结果永远不可能在新的战斗中复现。
    2.6.2 按下空格键开始按下空格键,程序将会为战斗双方都选择默认的长蛇阵,然后生成全局地图表开始运行。
    2.7 获胜判定对于两个生物体间的胜负判定不再赘述,这里主要介绍全局的胜负判定机制。
    由于葫芦娃阵营永远只有八个“生物体”,而妖精阵营有可能因为选择的阵型而导致生物体数目远远多于葫芦娃阵营。倘若葫芦娃生物体数目小于妖精阵营,为了平衡这种数目间的不对等,程序在双方阵营战斗开始之前对葫芦娃进行了一定程度的战力加持。
    在一场战斗中,若战斗双方生物体数目相同,则在三十秒之内,倘若一方全军覆没,则另一方获胜;若三十秒到达时,战斗双方都有生物体存活,则强制结束战斗,获胜方为“生物体存活数目相对占比较多一方”。比如葫芦娃一方还有两个生物体存活,妖精一方初始有15个生物体,最终还有三个生物体存活,则判定为葫芦娃获胜,因为葫芦娃存活数目占比为 1/4,妖精一方存活占比为 1/5.

    葫芦娃移动
    葫芦娃们永远是向前移动——除非碰到传送门。所谓传送门是指葫芦娃前方的“尸体”,只要葫芦娃们与妖精相遇并打败了对手,那么他们就可借助妖精身上残余的力量实现一次随机传送,随机传送总是趋向于把他们送到妖精的大本营中某一块空地。一旦葫芦娃走到妖精大本营的尽头,他就会在自己的“兵线”上从头巡视,如果这条兵线上仍有妖精的话。
    妖精移动
    蝎子精带领着妖精们“傻乎乎”的,只知道一股脑往前冲,遇到葫芦娃们,他们会与之决一死战,然后借着葫芦娃“尸体”上残余的神力完成一次随机传送。他们也是趋向于传送到葫芦娃的大本营中。妖精们不懂得寻找对手,他们永远只会呆在自己的兵线上不动——即倘若不传送,他们的y坐标不会改变。
    爷爷移动
    爷爷是葫芦娃阵营中重要而薄弱的一环,所以他总是在自己的大本营中来来回回巡视,除非有妖精冲破防锁,与他相遇,否则他一点危险也不会有。
    蛇精
    蛇精与爷爷移动方式基本相同,不过她不懂得绕开“尸体”。
    2.8 面向对象思想
    继承:Calabash,Grandpa,Monster,Snake,Scorpion,Empty这些类继承自Creature;阵型类继承自阵型主类
    封装:将setImage方法封装在InitImage类内部,对外部提供访问接口
    多态:设置阵型时,先生成具体生物体,然后将这个对象装入地图表中。地图表(fieldlist)是一个Creature主类的List,而每个对象是Creature类的子类。比如葫芦娃类(Calabash)继承自Creature

    fieldlist.set(0, new Calabash(...));
    三、优点与不足3.1 优点
    多阵型:程序为葫芦娃和妖精两方阵营提供了所有可能的阵型,玩家可自主选择阵型,在选择阵型后支持更换阵型
    平衡性:为了保证游戏的公平性,程序中对葫芦娃和妖精阵营的总体战力进行了平衡,以保证双方胜率大致相等
    不可重复性:每一局战斗都是独一无二的,几乎不可能有两局战斗会完全相同
    不可预测性:战斗的结果不可能被准确预测,即便某一方战至只剩一兵一卒,他也有可能翻盘获胜
    时间代价低:每局战斗限制在30s以内,等待战斗结束不会耗费太久

    3.2 不足
    趣味性有限:由于采用近战一决生死的方式,每两个生物体间的战斗结束太过迅速,趣味性较低
    文件读和写方法在不同包中实现:这里没有给文件读写一个比较合适的封装
    界面不够美观:原本应当给界面一个合适的场景,但因为设计上的一些缺陷,导致加载出的场景图片不能被很好的呈现出来。
    生物体属性不可见:每个生物体有他的能力侧重点,譬如大娃力大无穷,这些最终被描述为战斗力的一部分,但在观众看来,所有生物体都是以相同的速度随即进行战斗,界面上没能体现出生物体能力的不同
    文件路径不可选择:生成的记录文件被命名为test放在E盘下,如果玩家对自己磁盘进行了重命名或者本身没有E盘,程序运行将会出错

    四、实验中遇到的问题4.1 maven打包上的问题maven打包编译器的相关报错

    查看别人的工程中pom.xml文件,发现是自己没有在这个文件中指明编译器,添加以下内容后解决了问题。

    maven打包不可识别的符号

    当查看相关位置的字符后,发现是maven打包不支持中文字符,只好把工程中的中文字符全部改成英文。
    4.2 GUI显示上的问题debug过程中,单步调试运行,发现执行了move方法,地图上某个位置的生物体移动了位置,并且执行了相关的setImage方法,但界面上显示出来的仍然是初始的阵型。只有当测试方法执行完毕,所有生物体都移动一次后,界面上才会显示出最终结果。
    于是将每一次执行move方法后的地图打印出来,看到生物体确实移动了,但界面就是不刷新。这个问题令我百思不得其解,后来我的同学告诉我,在程序本身线程下,GUI界面就是这样显示的,只有所有动作结束,它才会刷新。
    4.3 开启多线程后的相关问题开启多线程后,遇到了很多无法查到解决方案的异常,一些是因为battle部分有问题到这错误,还有一些是多线程数据安全上的问题,其中有一个是JavaFX的Canvas相关报错。原因在于我给每个生物体对象的线程都传了这样一个参数 Canvas mainCanvas,他们用的是同一个Canvas,同一个Canvas只能得到一个二维像素点坐标,但多线程同时运行,会多次获得二维像素点坐标,所以导致了异常。
    解决办法:设置一个主线程,在主线程中开启子线程,mainCanvas只在主线程被调用,子线程只执行各个对象的move方法。
    4.4 Java语言相关问题字符串比较:对于字符串a和字符串b,c++中可以直接进行 a==b的判定,但在Java中没有重载该操作符,于是换用a.equal(b)来进行比较。
    五、参考资料
    理解Java特性之多态 https://www.cnblogs.com/chenssy/p/3372798.html
    Java的Integer和int的区别 https://blog.csdn.net/login_sonata/article/details/71001851
    JavaFX入门(二):JavaFX和FXML https://blog.csdn.net/theonegis/article/details/50181339
    Java中extends和implements的区别 https://www.cnblogs.com/sharpel/p/5859753.html
    使用maven来管理Java项目 https://www.cnblogs.com/tzyy/p/4768859.html
    Java中多线程 https://www.cnblogs.com/wxd0108/p/5479442.html
    JavaFX中的键盘事件 https://blog.csdn.net/alanzyy/article/details/48628991
    Java文件创建、删除、读写等操作 https://www.cnblogs.com/chen-lhx/p/5610678.html
    Java中多线程的测试和调试 http://www.cnblogs.com/wuyepeng/p/9733877.html
    深度解析runnable接口 https://blog.csdn.net/zxw136511485/article/details/53032658
    1 评论 1 下载 2019-04-18 19:36:59 下载需要7点积分
  • 基于C#实现的电影院售票管理系统

    一、引言1.1选题背景随着互联网和电子商务的快速发展,网上购物已经成了现代人生活中很重要的一种方式,如:数码产品、生活用品、化妆品护肤品等,只要是人们需要到的东西,基本都可以在网上购买。除了购买各种物品,现代人的生活也不再向过去一样单调,除了学习和工作之余,人们的娱乐生活也逐渐丰富,最普遍的娱乐休闲方式之一就是到电影院看电影,那么传统的电影订票窗口显然已经不能满足人们的需要了,所以开发一个电影院网上订票系统是非常必要和可行的。以前传统的电影票订票方法是去电影院的购票窗口查看电影的上映时间、场次、可选座位等信息再进行购票,人们往往需要排队才能买到电影票,这样不仅浪费了人们宝贵的时间,而且电影院工作人员的工作量也很大,对于这种低效率、浪费时间的事情,完全可以以网上购票的方式来改变。
    为了提高劳动的效率、节约成本、提高服务质量,我们小组开发了此款系统,用以方便影院的售票和客户的购买,通过这个系统,可以很快的实现会员注册、登录、购票,后台管理员可以新增影片、排片等基本操作。
    二、需求分析2.1 用户需求需要在网上购买电影票的用户可使用本系统,进行会员的注册登陆之后可以进行网上查询电影、购买电影票等操作,省去了去电影院实体窗口排队买票的繁 琐程序。
    2.2 系统功能分析
    新用户的注册、登录,用户数据能存在后台数据库中
    电影的录入、删除、查询、修改
    电影的排片
    管理员查询会员级别等信息
    会员查询影片信息
    会员购买电影票

    2.3 条件与限制系统可以实现一些基本的购票功能,但系统较简单,尚存在很多缺陷,不能实现完善和全面的功能。
    缺陷:

    首先要使用admin进行登录才能开始注册会员
    购票后无法查看购票信息
    不可以支持选座
    购票时不能通过搜索影片名字等来查找影片

    三、模块设计3.1 系统流程图系统流程图如图一所示:

    系统功能图如图二所示:

    3.2 系统使用指南对于用户
    首先,系统使用者先通过admin登录,进入到新用户注册页面,以管理员的身份为用户注册一个新的会员账号,已注册好的账号密码自动保存在后台数据库中,用户下次可以直接使用已注册的会员账号登录本系统进行电影的查询、购票等操作。
    对于管理员
    首先,管理员可以为新用户注册不同级别的会员账号、查看会员的信息。其次,管理员可在系统后台做电影的录入、删除、查询、修改等基本操作,除此之外,添加好影片后可以对电影进行的排片。
    四、数据库设计及实现4.1 系统E-R图E-R图如图三所示:

    4.2 逻辑结构设计(关系数据库设计)
    顾客(Cus、CusCard、CusType、CusTel(key))
    登陆(UserName、UsePwd、UserType)
    电影(Mname(key)、MBZ、MLanguage、MTYPE、MDirector、MACT、MTime)
    排片(MName(key)、MPrice、MRoom、Mcount、MTime、MBRQ、MERQ)
    上映(ID(key)、MName、MPrice、MRoom、MCount、MTime、MRQ)
    购票(PID(key)、Cname、GPCount、GPJE、MName、SYRQ、FYT、GPRQ、CZY)

    4.3 数据库主要代码及触发器会员信息
    CREATE TABLE [dbo].[Cus]( [CusTel] VARCHAR(50) NOT NULL PRIMARY KEY, [CusName] VARCHAR(50) NOT NULL, [CusCard] VARCHAR(50) NULL, [CusType] VARCHAR(50) NULL)
    触发器
    SET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGOALTER TRIGGER [dbo].[InsertULogin] on [dbo].[Cus]after insertasdeclare @username varchar(50)declare @userpwd varchar(50)select @username=CusTel from inserted select @userpwd= right(CusCard,6) from inserted insert into ULogin values(@username,@userpwd,'C')
    购票信息
    CREATE TABLE [dbo].[GP]( [PID] VARCHAR(50) NOT NULL PRIMARY KEY, [CName] VARCHAR(50) NOT NULL, [GPCount] INT NULL, [GPJE] FLOAT NULL, [MName] VARCHAR(50) NULL, [SYRQ] VARCHAR(50) NULL, [GPRQ] VARCHAR(50) NULL, [CZY] VARCHAR(50) NULL, [FYT] VARCHAR(50) NULL)
    触发器
    SET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGOALTER TRIGGER [dbo].[UpdatePP] on [dbo].[GP]after insertasdeclare @MName varchar(50)declare @SYRQ varchar(50)declare @count intselect @MName= MName,@SYRQ=SYRQ,@count=GPCount from inserted update MovieSY set mcount=mcount-@count where MName=@MName and MRQ=@SYRQ
    电影信息
    CREATE TABLE [dbo].[Movie]( [MName] VARCHAR(50) NOT NULL PRIMARY KEY, [MLanguage] VARCHAR(50) NOT NULL, [MDirector] VARCHAR(50) NULL, [MAct] VARCHAR(50) NULL, [MName] INT NULL, [MBZ] VARCHAR(255) NULL, [MType] VARCHAR(50) NULL)
    上映信息
    CREATE TABLE [dbo].[MovieSY]( [ID] INT NOT NULL PRIMARY KEY, [MName] VARCHAR(50) NOT NULL, [MRoom] INT NULL, [MCount] varchar(50) NULL, [MName] INT NULL, [MTime] VARCHAR(50) NULL, [MRQ] VARCHAR(50) NULL)
    排片信息
    CREATE TABLE [dbo].[PP]( [MName] VARCHAR(50) NOT NULL PRIMARY KEY, [MPrice] INT NOT NULL, [MRoom] VARCHAR(50) NULL, [MCount] INT NULL, [MTime] VARCHAR(50) NULL, [MBRQ] VARCHAR(50) NULL, [MERQ] VARCHAR(50) NULL)
    触发器
    SET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGOALTER TRIGGER [dbo].[InsertPP] on [dbo].[PP]after insertasdeclare @MName varchar(50)declare @BRQ varchar(50)declare @ERQ varchar(50)declare @MTime varchar(50)declare @MRoom varchar(50)declare @MPrice intdeclare @MCount intdeclare @MBRQ datetimedeclare @MERQ datetimedeclare @RQ varchar(50)select @MName= MName,@BRQ=MBRQ,@ERQ=MERQ,@MTime=MTime,@MRoom=MRoom,@MPrice=MPrice, @MCount=MCount from inserted set @MBRQ=CONVERT (datetime,@BRQ)set @MERQ=CONVERT (datetime,@ERQ)while @MBRQ<=@MERQbegin set @rq=CONVERT(varchar(50), @MBRQ, 112)insert into MovieSY values(@MName,@MPrice,@MRoom,@MCount,@MTime,@RQ) set @MBRQ=DATEADD(DAY,1,@MBRQ)endCREATE TABLE [dbo].[MovieSY]( [UserName] VARCHAR(50) NOT NULL PRIMARY KEY, [UserPwd] VARCHAR(50) NOT NULL, [UserType] VARCHAR(50) NULL)
    五、系统设计与实现5.1系统开发环境
    硬件环境

    Intel Pentium 166MHz或以上;内存:需要至少512MHZ;
    软件环境

    运行于Windows2010版的操作系统之上;SQL Server2008数据库;Visual Studio2013;

    5.2 功能模块
    输入功能模块
    查询显示功能模块
    查询、售票功能模块
    登录、注册功能模块

    5.3 系统主要页面展示登录、注册页面

    登陆后的页面

    会员注册页面

    会员信息查询页面

    添加影片页面

    查询修改影片页面

    排片页面

    “排片”页面通过连接数据库,可以搜索影片的名字、导演、主演来选择影片,再输入票价,放映大厅,座位数,放映时间等信息,最后确认排片。
    购票页面

    购票页面通过对获得的已经被排片的电影进行选择而进行购票这一操作。在购票前需填入购票数量、联系电话,系统会自动计算合计金额。之后点击购票按钮则能进行购票操作。但一旦购票成功则不能进行退票操作,也不能查看所购买的电影票。
    数据库页面

    六、主要特色6.1 系统实用性系统首先可以进行新用户的注册,用户数据能存在后台数据库中。管理员可以进行电影的录入、删除、查询、修改,添加好影片后进行电影的排片。退出登录后再使用之前注册的会员登录,可以购买电影票。
    6.2 突出优势和特色(创新点)
    购票时可以看到影片的导演、主演等信息
    用户分为钻石用户,白金用户和普通用户,对用户进行了分类

    7 小结数据库技术课程设计是一次对课堂所学知识的灵活应用,是理论知识与实践的相结合。经过了一周的课程设计,本系统基本达到了当初的设计要求,设计上也基本合理。我们不仅对数据库系统的认识更加深入,同时也掌握了面向实体的系统分析的基本方法,对VS也有了新的认识,也知道了要有坚持不懈,不惧困难的精神,才能取得成功。一个简单的系统,每一个细节都需要在实践中去挖掘并进行进一步的修改完善。本次课设让我们受益匪浅,在分析问题以及解决问题等方面的能力有所提高,也是一次很好的同学之间交流合作的机会。数据库技术的用途很广,还有很多值得我们学习,希望今后能有更多这样的机会。
    3 评论 15 下载 2019-01-01 18:20:12 下载需要18点积分
  • 基于python的骑士游历问题解析


    系统:windows
    环境:codeblocks
    语言:C++

    一、需求分析在棋盘上,骑士只能走日字(L 形).假设骑士在(0,0),我们希望用最少的移动步数使他走到(x,y)(例如,从(0,0)到(1,1)需要两步,骑士可以移动到棋盘的负坐标处)。
    要求:设计一个可采纳的启发式函数,来估计需要的最小移动步数值,保证结果足够地精确。 用 A*算法和你的启发式函数来编程实现求解。结果输出详细过程。
    证明你的函数是可采纳的。
    二、问题重述
    将路径长度规定为移动步数。
    所得解路径必为哈密顿路径。
    根据对称性,可以看出需要到达负坐标之后再到达目的地的路径,完全可以先到达对称处的正坐标点,再到达目的地。

    如下图:

    s —> t1:可以找到相应的对称点A1’;
    s —> t2:可以找到相应的对称点B1’和B2’;

    并且路径长度是相同的。

    三、算法分析3.1. 算法步骤具体步骤:

    把初始节点S0放入open表中,令f(S0)=0
    如果open表为空,则问题无解,退出。
    把open表中选取第一个节点(节点n)从open表中移到closed表中。
    考察节点n是否为目标节点,若是则求得问题的解,并退出;否则继续.
    如果节点n可扩展,则转6,否则转2
    扩展节点n,对其后续节点集合M中的某一个节点m(xm,ym),计算f(x,y)=g(x,y)+h(x,y) ,把open表中的节点按照f值从小到大排序。

    其中g(x,y)是从S0(x0,y0)到当前状态m(xm,ym)的步数
    初始条件



    可根据父节点n(xn,yn)递推获得g(x,y)


    h(x,y)是当前节点到目标状态T(tx,ty)的估计步数
    先令


    所以h(x_n,y_n)函数形式可写为:


    为每个节点设置指向n的指针
    转向第2步

    3.2. 证明算法是A*3.2.1 可采纳性根据前人的定理,A*算法具有可采纳性,所以只需要证明本文算法为A*算法,所以只需证明两个条件:
    对于评判函数

    只需证明:

    (1)式由于递推的关系容易知道是正确的,起点x为特例g(x) = 0。
    对于(2)式,图上两点最短距离为曼哈顿距离dt,而L形走法每次最多走3步,而且因为是L形限制,可能会走更多的路,所以容易得到

    可见该算法是A*算法,有可采纳性。
    四、算法实现与优化4.1. 相关变量的定义4.1.1 bili类与pair<int,int>类
    bili类
    存储状态节点和估计函数,方便之后的open表进行比较
    struct bili{ int x,y,l; // x,y代表横纵坐标,l代表耗估计函数f(x,y); bili() {} bili(int _x,int _y,int _l):x(_x),y(_y),l(_l) {}};

    pair<int,int>类(pii)
    pair这里使用pii类是为了方便对状态(形如(x,y))的管理,为了方便open表比较耗散值大小,所以写了一个bili类来重写“<”号。

    typedef pair<int, int> pii;



    元素
    功能




    first
    相当于x


    second
    相当于y


    make_pair(x,y)
    Construct pair object: pair<x,y>



    4.1.2 open表bili heap[maxn],open[maxn];int sz = 0;
    4.1.2.1 采用小根堆优化实现。小根堆,又名最小堆,是一种经过排序的完全二叉树,其中任一非终端节点的数据值均不大于其左子节点和右子节点的值。
    4.1.2.2 分析如果用循环数组模拟open表,则每次都需要对数组进行排序,并且将估计耗散值最小的状态取出进行下一步操作。我会采用插入排序。
    复杂度为:

    而小根堆这个数据结构的功能采用堆排序的原理将一堆数据中估值最小的状态置于队列的顶端,方便取出和使用。
    复杂度为:

    4.1.3 closed表int g[maxm][maxm];vector<pii> closed;

    用数组g标记状态S是否已经路过,降低判断S是否已经路过时的复杂度。
    closed用来存储使用过的状态,降低打印的复杂度。
    判断复杂度:



    打印复杂度:


    总的复杂度:

    4.1.4 设置指向n的指针pii pre[maxm][maxm];//pre[x][y]表示(x,y)的上一个状态//设置指向父节点的指针pre[xx][yy] = make_pair(a.x, a.y);
    具体实现逻辑请看代码。
    4.1.5 估值函数h(x,y)int h(int x,int y){ int dt = abs(x-tx)+abs(y-ty); if (dt%3==0) { return dt/3; } return dt/3+1;}
    4.1.6 打印函数该函数存在的价值是打印每次从open表取出父节点,扩展出子节点之后的棋盘的状态,即可以展示每次增加了哪些子节点,具体效果请看使用说明书。
    void print_status(){ printf("\nboard:\nS: starting point, T: ending point;\nK : points that can be reached now, . :points that cannot be reached now.\n\n"); for (int i=0;i<m;i++){ for (int j=0;j<n;j++){ if(i==sx&&j==sy){ printf("S "); }else if (i==tx&&j==ty){ printf("T "); }else if (g[i][j]>0){ printf("K "); }else printf(". "); } printf("\n"); } printf("\nopen表:\n"); for (int i=0;i<sz;i++){ open[i] = heap[i]; } sort(open,open+sz,cmp); for (int i=0;i<sz;i++){ printf("(%d, %d) ",open[i].x,open[i].y); } printf("\nclosed表:\n"); for (int i=0;i<closed.size();i++){ printf("(%d, %d) ",closed[i].first,closed[i].second); } printf("\n"); system("pause");}
    4.1.7 其余变量解释const int maxm = 12; //棋盘最大尺寸const int maxn = 150; //open表最大尺寸int m,n,sx,sy,tx,ty,cnt=0; //cnt用于记录Step,比如Step 1// m,n输入的棋盘尺寸// sx,sy 起点, tx,ty 终点int g[maxm][maxm];// g(x,y)int dx[8] = { 1,2,2,1,-1,-2,-2,-1 };int dy[8] = { 2,1,-1,-2,-2,-1,1,2 };
    4.2. 整体实现4.2.1 代码int bfs(int x,int y){ bili in(x,y,0); push(in); //closed.push_back(make_pair(x,y)); while(sz!=0) { bili a = pop(); closed.push_back(make_pair(a.x,a.y)); //printf("a: (%d,%d)",a.x,a.y); if (a.x == tx && a.y == ty) return g[tx][ty]; printf("\n-------------Step %d-------------\n",++cnt); for (int i=0;i<8;i++){ int xx = a.x+dx[i]; int yy = a.y+dy[i]; if (check(xx,yy)&&g[xx][yy]==0){ g[xx][yy] = g[a.x][a.y] + 1; bili nw(xx,yy,g[xx][yy] + h(xx,yy)); pre[xx][yy] = make_pair(a.x,a.y); push(nw); } } print_status(); } return -1;}
    4.2.2 bfs函数流程
    将起点放入小根堆(heap),进入2。
    如果小根堆为空,进入6;否则进入3。
    把小根堆的堆顶元素a取出,如果堆顶元素为终点,进入5;否则,进入4。
    计算a的下一步能走到的合法的点(a的子节点),将a的子节点放入小根堆,使子节点指向a,即pre(son) = a,进入2。
    返回路径长度,函数结束。
    返回-1,函数结束。

    五、测试Step 1 输入棋盘大小建议输入大小为8*8的棋盘,棋盘每行每列的下标从0开始。

    Step 2 输入起点请按照x y的格式输入,这里假定起点为(0,0)。

    Step 3 输入终点这里选择(6,6)做为终点。

    Step 4 连续回车获得结果这里的open表中放置的是图中K的坐标,closed表加入已经扩展过的节点, 这里是刚开始利用起点(0,0)进行扩展。

    多次回车,如果不想回车,可以将以下源代码中print_status()函数中的system("pause")语句注释后重新编译。

    可见Reslut中的最优路径符合预知。
    Step 5 总结可视化
    利用字符串实现了简单可视化,不想用OpenGL实现可视化,感觉要学很久,配环境就配了半小时,学的话学到三角形就开始难受。Qt的话,电脑硬盘空间不够了。嗯,其实想用python,但是现在的我已经可以用简单的可视化让自己安心了,就这样吧,谢谢观看。
    1 评论 1 下载 2019-04-18 15:40:26 下载需要6点积分
  • 基于java和Sql Server数据库的停车场管理系统

    一、实验内容:实现停车场管理系统,应用于车辆的出、入管理。
    二、功能要求:包括车辆进出管理与系统管理等功能模块,可根据车辆停放时间及收费标准自动收费。用户需要事先办理停车卡并充值,停车卡分优惠卡和普通卡两类。

    车场管理:车辆入场、车辆出场
    信息查询:某时间段的出入场信息,当前在场信息,车辆历史停车记录及收费信息
    信息维护:用户及停车卡信息维护、充值等
    系统管理:车位信息,计费标准等

    系统包含两类用户:管理员用户和普通用户。
    管理员可以使用系统所有功能,普通用户只能查询车辆历史记录、用户信息、停车卡充值,查询计费标准。
    三、实验环境:
    Windows XP
    JDK 1.6
    Eclipse
    SQL Server
    备注:

    在XP平台开发DK(JavaDevelopment Kit)是Sun Microsystems针对Java开发员的产品Eclipse进行前台和程序设计,开发图形用户界面和停车收费功能实施
    SQL建立数据库

    四、需求分析与设计:4.1 需求分析:本软件具有如下主要功能:

    本系统包括两类用户:管理员用户和普通用户。管理员可以使用系统所有功能,普通用户只能查询车辆历史记录、用户信息(只限于个人信息)、查询计费标准、查询当前在场信息、查询出入场信息、当前可用车位信息、口令修改。具体模块划分为如下模块:车场管理模块、信息查询模块、信息维护模块、系统管理模块。
    车场管理模块:(应该分为车辆入场和车辆出场两部分)

    车辆入场功能描述:车辆进入停车场时进行登记,记录入场时间并指定车位。只有具有停车卡的车辆才可进场,没有办理停车卡的车辆,应先办理车卡。如果没有相应车位,不能入场;如果卡中余额低于100元,应先充值后再入场。满足条件的车辆,为其指定车位并记录入场时间。车卡分两种类型普通型和优惠型。车辆出场功能描述:车辆开出停车场时进行登记,记录出场的时间并进行自动收费(从卡上扣除)。根据车辆进场时间,出场时间及收费标准自动计算车主应该缴纳的费用。如果停车时间包含不足一小时的时间,超过30分钟按一小时计算,不足三十分钟不计算。如果卡上余额足够则直接扣除;如果卡上余额不足,则应先充值后再扣除相应费用。
    信息查询模块功能描述:在这个模块里用户可以查询出入场信息、当前在场信息、用户个人信息、用户历史记录、收费标准以及当前可用车位信息
    查询出入场信息功能描述: 查询当前在场信息户可以在这里查询到两种车位的总量及当前可有的车位数量。
    查询用户个人信息功能描述:登录的管理员可以根据卡号和名字查询用户信息。登陆的普通用户只可以查到自己的信息。
    查询用户历史记录功能描述:用户可以输入卡号查询相应卡号的历史记录,包括车位号、开始停车时间、结束停车时间、停车总时间、相应收取的费用。
    收费标准功能描述:用户可以在这里查询不同种类的车位和不同卡的计费标准。
    当前在场信息功能描述:用户可以在这里查询到当前在场的车辆信息,包括卡号,车位号,开始停车时间。
    当前可用车位信息功能描述:在这里用户可以查询当前可用的车位的信息,包括车位号、车位类型。
    信息维护模块在这个模块里用户可以实现用户注册、用户修改及用户充值
    用户注册功能描述:在这里管理员可添加新的用户(普通用户)。
    用户修改管理员在这里可以修改用户。这里会以表的形式显示所有的用户信息,包括用户的停车卡信息维护,充值信息等。管理员点击相应的一行用户信息,这行信息会自动填充到表下的面板里,用户可以在面板里修改用户信息,面板下面有两个按钮,修改、删除,点击相应的按钮可以实现相应的功能。
    用户充值功能描述:用户可以再这里查到自己的余额,并且可以在这里完成充值。
    系统管理模块功能描述:在这个模块里可以修改相应的车位信息计费标准、注册管理员、更改用户口令以及查看系统声明信息。
    管理员注册功能描述:管理员可以在这里添加新的管理员。
    更改口令功能描述:用户可以在这里更该自己的密码。注:操作员只可以修改自己的密码。
    计费标准管理功能描述:管理员可以在这里不同车位类型、不同车卡类型的收费标准。
    关于功能描述:用户可以在这里看到系统声明。

    4.2 界面设计登陆界面

    管理员主界面

    普通用户主界面

    车辆入场界面

    车辆出场界面

    计费标准界面

    当场在场信息界面

    用户历史信息界面

    用户个人信息界面

    普通用户个人信息界面

    出入场信息界面

    当前可用车位信息界面

    用户注册界面

    用户修改界面

    用户充值界面

    管理员注册界面

    更改口令界面

    计费标准管理界面

    关于界面

    五、数据库设计5.1 数据库关系图
    5.2 数据表的结构设计


    用户表:users








    字段名称
    数据类型
    可空
    默认值
    说明


    cardid
    int
    不可

    主键,用户的停车卡号


    name
    Nvarchar(20)
    不可

    用户姓名


    password
    Nvarchar(20)


    用户密码


    cardtype
    Nvarchar(20)


    停车卡类型


    userstype
    Nvarchar(20)


    用户类型


    carid
    int


    用户车牌号


    tel
    int


    用户电话号码


    overage
    int


    用户余额






    车位信息表:sit_infor








    字段名称
    数据类型
    可空
    默认值
    说明


    stationid
    int
    不可

    主键,车位号


    stationtype
    Nvarchar(20)
    不可

    车位类型






    停车收费卡收费表:charger








    字段名称
    数据类型
    可空
    默认值
    说明


    cardtype
    Nvarchar(6)


    车卡类型


    stationtype
    Nvarchar(20)


    车位类型(车卡类型与车位类型一起作为主键)


    charge
    int


    价格






    停车表:park








    字段名称
    数据类型
    可空
    默认值
    说明


    cardid
    int


    车卡号(外键)


    stationid
    int


    车位号(外键)


    parkid
    int

    1,每次增加一
    停车号,主键


    startpark
    datetime


    停车开始时间


    endpark
    datetime


    停车结束时间


    fee
    int


    停车的收费


    sumpark
    int


    停车总时间



    六、关键技术介绍6.1 在其他类中得到当前登录用户对象 实现方法:在LoginFrame类中设置两个静态方法,在其他类中只需要引入LoginFrame类,然后调用他的静态方法即可。方法体如下:
    public static users getUser() { return user; } public static void setUser(users user) { LoginFrame.user = user; }
    6.2 实现用户类型不同,主界面不同的功能 可以定义静态方法disMenu().当用户是普通用户时,调用disMenu()方法即可。具体实现如下
    public void disMenu() { mnuPark.setEnabled(false); mnuSever.setEnabled(false); mnuManZhuCe.setEnabled(false); mnuManCharge.setEnabled(false); } if(user.getUserstype().equals("管理员")) { MdiFrame frame1 = new MdiFrame();//创建一个主窗体 frame1.setVisible(true);//设置其可见 LoginFrame.this.setVisible(false);//设置登录窗体为不显示 } else {//判断用户名是否为null MdiFrame frame = new MdiFrame();//创建一个主窗体 frame.disMenu(); frame.setVisible(true);//设置其可见 LoginFrame.this.setVisible(false);//设置登录窗体为不显示 }
    6.3 怎么得到系统时间 SimpleDateFormat myfmt=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); String a4 = myfmt.format(new java.util.Date()).toString();
    6.4 怎么计算时间差值 try { java.util.Date now = myfmt.parse(a3);//a3是系统当前时间(即出场时间) java.util.Date date=myfmt.parse(a7);//a7是入场时间 int l=(int) (now.getTime()-date.getTime());//计算毫秒差值 day=l/(24*60*60*1000);//获取天数 hour=(l/(60*60*1000)-day*24);//获得小时 min=((l/(60*1000))-day*24*60-hour*60);//获得分钟 } catch (Exception e1) { JOptionPane.showMessageDialog(null,"消费计算错误"); } if(min < 30)//如果分钟小于30分钟 a8 = day*24+hour; else //如果分钟大于30分钟 a8 = day*24+hour+1;
    6.5 怎么让布局更优美 使用布局管理器; GridBagLayout,以更改密码界面为例:
    getContentPane().setLayout(new GridBagLayout()); setBounds(234, 129, 285, 223); final JLabel label_5 = new JLabel(); label_5.setText("登 录 名:"); final GridBagConstraints gridBagConstraints_11 = new GridBagConstraints(); gridBagConstraints_11.gridy = 2; gridBagConstraints_11.gridx = 0; getContentPane().add(label_5, gridBagConstraints_11);
    七、系统实现功能结构图
    6 评论 175 下载 2018-11-19 09:31:15 下载需要10点积分
  • Python实现的基于Scrapy爬虫框架和Django框架的新闻采集和订阅系统

    摘要随着互联网的迅速发展,互联网大大提升了信息的产生和传播速度,网络上每天都会产生大量的内容,如何高效地从这些杂乱无章的内容中发现并采集所需的信息显得越来越重要。网络中的新闻内容也一样,新闻分布在不同的网站上,而且存在重复的内容,我们往往只关心其中的一部分新闻,网络中的新闻页面往往还充斥着大量许多与新闻不相关的信息,影响了我们的阅读效率和阅读体验,如何更加方便及时并高效地获取我们所关心的新闻内容,本系统能够帮我们做到这一点。本系统利用网络爬虫我们可以做到对网络上的新闻网站进行定时定向的分析和采集,然后把采集到的数据进行去重,分类等操作后存入数据库,最后提供个性化的新闻订阅服务。考虑了如何应对网站的反爬虫策略,避免被网站封锁爬虫。在具体实现上会使用Python配合scrapy等框架来编写爬虫,采用特定的内容抽取算法来提取目标数据,最后使用Django加上weui来提供新闻订阅后台和新闻内容展示页,使用微信向用户推送信息。用户可以通过本系统订阅指定关键字,当爬虫系统爬取到了含有指定关键字的内容时会把新闻推送给用户。
    [关键词]网络爬虫;新闻;个性化;订阅;Python
    AbstractWith the rapid development of the Internet, the Internet has greatly enhanced the production and dissemination of information, the networkwill produce a lot of content every day, how to find and collectthe information we needed from these disorganized contentefficiently is more and more important. The news content on the network is thesame, the news is distributed on different sites, and there are many duplicate content, we only care about part of thenews usually. The network news pages areoften filled with a lot of news and information is not related that impact ourreading efficiency and readingexperience. How to more convenient and efficient access to the news we are concerned about the content, thissystem can help us to do this. This system uses the web crawler to collect news on the network site. And then toclassify data and other operations like delete the duplication,store data byuse the database, and finally providepersonalized news subscription service. This system has considered how to deal with the sit&s anti-reptile strategy, toavoid being blocked by the site crawler. In the concrete implementation, I will use Python with Scrapy framework towrite the crawler, then use a specificcontent extraction algorithm to extract the target data, and finally use Django and WeUI to provide news subscription background and news contentdisplay page, use WeChat to push information to users. Users can subscribe tothe specified keywords through the system, system will push thenews to the user when the crawler system crawled the contents contains thespecified keyword.
    [Keywords] Web Crawler;News; Personalization; Subscription; Python
    第一章 引言1.1 项目的背景和意义如今我们所处的时代是一个信息时代,信息处处影响着人们的生活,无论是个人还是企业,都希望能够获取自己所关心的内容。人们获取信息的方式渐渐从传统的纸质阅读转移到了信息传播速度更快互联网的在线阅读上,而许多媒体和互联网企业都推出了各自的新闻门户来提供新闻内容阅读和检索等功能,但是这些新闻信息仍需要我们主动去访问这些网站才能获取到,而且我们还要在这些新闻中筛选出自己所关心的内容进行阅读,这样浪费了我们许多阅读之外的时间。网络中的新闻分布在不同的网站上,我们往往只关心其中的一部分新闻,网络中的新闻页面往往还充斥着大量许多与新闻不相关的信息,影响了我们的阅读效率和阅读体验,如何更加方便及时并高效地获取我们所关心的新闻内容,这是一个急需解决的问题,本系统就是为了解决这样的痛点而产生的。
    1.2 研究开发现状分析1.2.1 个性化新闻服务现状如今国内外存在众多提供个性化新闻服务的互联网公司,如著名的ZAKER和今日头条,这些公司的产品都能够根据你的兴趣爱好来展示和推荐你喜欢的内容,这种创新性已经颠覆了传统的新闻资讯平台的市场格局,大众纷纷表现出对这些个性化新闻平台的追捧。据最新的《中国互联网络发展状况统计报告》显示,截至2016年12月,中国网民规模达7.31亿,移动互联网用户规模达到6.95亿,其中新闻资讯领域行业用户规模达到6.14亿,年增长率为8.8%,在移动端渗透率达到82. 2%。 [1]新闻资讯信息的用户需求也更加细分,用户对内容的需求也更加精细,除了方便阅读、时效性高、趣味性强外,个性化推荐方式也越来越受到用户的关注。在猎豹全球智库发布的安卓2016年1月新闻类APP排行榜中,作为个性化新闻平台的今日头条、一点资讯皆排在移动资讯APP的前三位。而前三中采用传统编辑推荐方式的只有腾讯新闻,可见如今个性化新闻平台已经成为绝对的主流。
    1.2.2 网络爬虫研究现状网页抓取工具是一种根据特定规则来自动获取互联网信息的脚本或程序。[2] 网页或搜索引擎等网站通过爬虫软件来更新自己的网站内容或者是更新对其他网站的索引。一般来说,网络爬虫会保留被抓取的页面,然后用户可以通过搜索引擎后来生成的索引进行搜索。因为爬虫访问网页的方式与人类相似,而且一般比人类访问的速度要快,会消耗访问的网站的系统资源。因此爬虫在需要大量访问页面时,要考虑到规划和负载等情况,否则容易被网站禁封。网站站长可以使用robots.txt文件来告诉爬虫访问的规则。robots.txt文件是一个具有指定格式的文件,网站站长可以通过此文件来要求爬虫机器人不能访问某些目录或者只能访问某些目录。互联网上的页面极多,而且数量一直在增长,即使是像谷歌这样的爬虫系统也无法做出完整的索引,因此在某些地方会根据需求来做一些主题化的爬虫,这样的爬虫爬到的结果往往能够更加精确。
    1.2.3 项目的范围和预期结果本文描述了基于网络爬虫的新闻订阅系统的设计与实现的过程,主要工作如下:

    编写一个网络爬虫,使其能够对网络中指定站点的新闻进行自动收集并存入数据库;
    数据的去重和网络爬虫的反爬虫策略应对;
    提供一个新闻展示页面,把爬取到的新闻展示给用户;
    提供新闻订阅页面,用户可以在页面输入指定订阅的关键词;
    编写微信推送服务,把用户订阅的新闻通过微信推送给用户;
    1.3 论文结构简介本论文的结构安排如下:

    第一章,引言。主要介绍了论文选题项目的背景、意义和目的;以及对相关领域中已有的研究成果和国内外研究现状的简要评述;介绍本系统涉及的范围和预期结果等。
    第二章,技术与原理。主要介绍本系统中所用到的主要技术和理论。
    第三章,系统需求分析。使用用例析取和用例规约等系统分析方法对本系统进行了需求分析。
    第四章,新闻采集与订阅系统的设计。介绍了系统的架构与原理,讲述本系统的各大模块的设计以及数据库的设计详情。
    第五章,新闻采集与订阅系统的实现。介绍本系统具体的实现过程以及实现的效果。
    第六章,系统部署。介绍本系统的部署环境与部署方法。
    第七章,总结与展望。对本系统所做的工作进行总结,提出了需要讨论的问题和一些本系统中可以改进的地方。

    第二章 技术与原理2.1 技术选型2.1.1 Python语言介绍Python是一种面向对象、解释型的计算机程序编程语言。它包含了一个功能强大并且完备的标准库,能够轻松完成很多常见的任务。它的语法比较简单,与其它大多数程序设计语言使用大括号把函数体包起来不一样,它通过缩进来定义语句块。[4]使用P帅on能够高效灵活地实现开发的任务,内置库以及大量的第三方库能够在许多地方避免重复造轮子的现象,有时使用c++语言来实现的一个功能可能需要几十行,Python只需要几行就足够了。与传统的脚本语言相比,Python拥有更佳的可读性和可维护性。这门语言的强大吸引到了许多开发者,拥有比较热门的Python社区,许多开发者在维护着这种Python编写的库,影响力也在日益增强。在网络爬虫领域,Python这门语言的使用也比较广泛,留下了大量的前人的学习研究的资料。基于以上优点,我选择了使用Python来开发本系统的网络爬虫部分和展示部分的服务端。
    2.1.2 Scrapy框架介绍Scrapy是一个纯Python基于Twisted实现的爬虫框架,用户只需要定制开发几个模块就可以方便地实现一个爬虫,用来抓取网页内容、图片、视频等。它最初是为了网站页面抓取所设计的,也可以应用在获取网络应用API所返回的各类数据或者是编写通用的网络爬虫。Scrapy用途比较广泛,可以应用于数据挖掘、自动化测试和数据监控等场景。Scrapy提供了一些网络爬虫中比较通用的中间件和模块等,也可以方便地编写自己所需的中间件来对爬取结果进行处理,只要在配置里面引用这些中间件就可以了。使用Scrpay来编写爬虫可以降低很多需要重复编写的爬虫处理代码所带来的成本。
    2.1.3 Django框架介绍Django是最早由Python实现的最着名的Web框架之一,最初是由美国芝加哥的Python用户组来开发的,拥有新闻行业背景的Adrian Holovaty是Django 框架的主要开发人员之一。在Adrian的领导下,Django团队致力于为Web开发人员提供一个高效和完美的Python开发框架,并授权开发人员根据BSD开源协议许可证免费访问。Django是一个高效的Web框架,可以帮助我们减少重复的代码,并把更多重点放在Web应用程序上的关键之处。在架构上,Django 跟Scrapy类似,也提供了中间件等,配置的方式也是类似的,使用类似的技术架构可以减少学习成本。本系统中我选用Django作为新闻订阅的服务端来提供API。
    2.1.4 MongoDB数据库介绍MongoDB是一个由C++语言编写的高性能,无模型的开源文档型数据库,是当前NoSQL数据库产品中最具有代表性的一种。MongoDB是使用文档来作为对象存储的,一条记录对应一个文档,集合类似传统的关系型数据库中的表,集合中存放的是那些具有同一特征或者属性的文档。在一个集合中,不同文档拥有的属性可以是不同的,这就是与传统的关系型的数据库的重点了,传统的关系型数据库要求表里的数据所拥有的属性格式都是一致的,MongoDB这种灵活性更利于文档映射到一个对象或一个实体上。对于需要经常改动数据格式或者数据格式不定的一些需求来讲,这种数据格式更为合适。MongoDB在读写性能方面也远超传统的关系型数据库的代表之一的MySQL。在本系统中我使用MongoDB 来存储爬取到的数据以及用户数据等。像MongoDB这样的非关系型数据库更合适储存爬虫数据,因为爬虫数据量可能比较大,数据之间关系型也不强。 MongoDB的性能也比传统的关系型数据库代表MySQL之类要强。
    2.1.5 AJAX介绍AJAX(异步的JavaScript + XML)本身并不是一种技术,它是由Jesse James Garrett在2005年提出的一个术语,描述了一种需要结合使用大量已经存在的技术的方式,包括HTML, JavaScript, CSS, DOM, JSON, XML等,还有最重要 JavaScript中的的XMLHttpRequest对象。当这些技术以AJAX模型的方式聚合时,Web应用程序可以更迅速地,无需加载整个页面就能更新全部或者部分的用户界面。这使Web应用能够更快地响应用户行为,带来更友好的用户体验。尽管在AJAX中X代表XML,但现在JSON使用的更多,因为JSON具有许多XML不具备的优势,比如它更轻量并且是JavaScript的一部分,各个程序语言都能够轻松解析JSON格式的数据。在AJAX模型中,JSON和XML的作用都是承载信息。[6]本系统会在新闻订阅和展示部分的前端使用AJAX来跟服务端进行交互,以达到前后端分离的目的。
    2.2 相关原理介绍2.2.1 网络爬虫介绍网络爬虫(英语:web crawler),也叫网络蜘蛛(spider),是一种自动提取网页的程序,它为搜索引擎从万维网上下载网页。传统的爬虫的启动从一个或多个初始网页开始的,从这些初始网页上获得接下来要爬取的URL,在抓取网页内容的过程中,不断从当前页面的内容上抽取新的需要继续爬取URL放入队列,直到满足系统的一定停止条件。[7]网络爬虫的抓取策略大致可以分为以下三类:广度优先搜索策略、深度优先搜索策略、最佳优先搜索策略等。本系统的爬虫部分使用的爬虫策略是广度优先搜索策略,因为本系统的网络爬虫具有针对性,所以爬取的层数不会很多。
    2.2.2 关键词提取技术通过分析文本,利用关键词抽取技术可以抽取出文本的关键词,关键词能够简单地反映出文本的主要内容,使人们更加直观方便地了解到文本内容的主题。关键词提取的技术有许多种,最常用的应该是基于统计的方法的TF-IDF算法。 TF-IDF(term frequency-inverse document frequency)是一种常用的用于数据挖掘与信息检索的加权技术。[8]词语的重要性是在TF-IDF算法中主要是由它在文中出现频率决定的。
    Jieba是一个基于Python的中文分词库,支持三种分词模式:精确模式、全模式和搜索引擎模式,它能够基于Trie树结构来实现高效的词图扫描,生成句子中汉字所有可能成词情况所构成的有向无环图,采用了动态规划查找最大概率
    路径,找出基于词频的最大切分组合。在本系统中,将考虑使用Jieba分词基于 TF-IDF算法的关键词抽取来抽取出爬取到的新闻内容的关键词。
    2.2.3 智能推送技术当我们使用如今的一些应用程序时,会经常收到来自这些应用的推送,过多的、不适当的推送会打扰到用户,互联网技术在最近几十年里已经得到了很大的发展,但是推送通知技术仍旧停留多年以前。为了实现智能化推送,我们需要搜集和分析能够帮助我们实现智能化推送通知的用户数据,这些数据的来源可以是用户的设置或者用户在应用中产生的数据等。在智能推送通知中,与时间相关的,个性化的,有帮助的,有联系性的是智能推送通知中四个基本的特征。
    在推送的时间上,我们可以做到时间智能化,时间智能化指的是推送的时间要恰当,推送发生在不恰当的时间比无用的垃圾推送消息造成的不良效果更验证。在不恰当的时候推送的通知不但打扰到用户,还很容易会被用户忽略。智能推送应该能够做到自动解决推送时间不恰当的问题。具体的实现可以通过一个对推送信息的重要性评估的引擎来决定消息推送的时间。
    在个性化上,我们可以把推送机器人设计成人类的形态,比起传统的系统消息,拟人化的方式能够让人更加容易接受,如苹果的Sin和微软的Cortana。当我们将来自机器智能的推送通知语言根据用户自己的特点进行调整后,用户在查看时看到的是更像交流式的风格,会感到更有亲切感,更加个人化。
    在推送的内容上也要慎重选择,根据不同用户来推送不同的内容。因为对于用户来说,只有用户关心的内容才是对用户有帮助的。拿新闻订阅应用来讲,就是用户只会对某些主题内容的新闻感兴趣,应用要做的就是把新闻的主题进行分析,打上不同的标签。然后找到用户兴趣中含有这些标签用户进行推送。如果不经处理对全部用户进行统一的推送,用户需要花费大量时间在这上面去过滤出自己感兴趣的内容。
    在推送内容的数量上也有讲究,如果一个服务高频次地的使用通知推送,用户可能会感觉到被冒犯然后会关掉它,后面的推送就都收不到了。因此,将推送的内容进行分组就很重要了。系统可以把一些相似的通知进行分组合并,可以减少对用户的打扰,在消息很多的时候这种优势就很明显了。在信息较少的时候可以选择把这些推送通知进行展开,因为此时用户可能会比较关心这些少见的内容,也是一个不错的选择。
    第三章 系统需求分析3. 1 新闻订阅系统用例析取基于网络爬虫的新闻采集与订阅系统要实现新闻数据抓取,数据过滤,数据筛选,数据展示,新闻订阅,推送等服务和功能,本系统用例图如图3.1所示:

    本系统主要用于以下几类人员:

    数据管理员,完成数据的抓取,过滤与筛选,新闻的推送,以及本系统管理维护等。
    用户,在网页上进行新闻订阅,通过微信接收订阅新闻的推送,点击进入对应新闻展示页面等。

    3.2 新闻订阅系统用例规约3.2.1 新闻订阅3.2.1.1 简要说明本用例允许用户增加或者删除自己订阅新闻的关键字,以及对已经订阅的关键字进行确认等操作。
    3.2.1.2 参与者用户。
    3.2.1.3 事件流基本事件流:用例开始于用户进入新闻订阅页面进行操作。

    订阅新闻关键字的状态共有两种,分别为“已订阅”、“未订阅”。顾客可以在相应的状态下进行操作,选择增加关键字或者删除关键字。
    如果关键字状态为“未订阅”,用户可以增加该关键字到自己的订阅列表中,本用例结束。
    如果关键字状态为“已订阅”,用户可以选择删除该关键字,本用例结束。
    无特殊要求。
    前置条件:本用例开始前用户必须是微信已登录状态。
    后置条件:如果用例成功,用户的订阅列表将被更新。
    活动图

    3.2.2 新闻推送3.2.2.1 简要说明本用例允许数据管理员根据新闻的关键字向已经订阅该关键字的用户进行推送等操作。
    3.2.2.2 参与者数据管理员。
    3.2.2.3 事件流基本事件流:用例开始于爬虫系统采集到新闻时。

    系统将新闻内容根据算法与用户订阅的新闻关键字作对比,对比结果分别为“匹配”、“不匹配”。如果匹配状态为“匹配”,系统将调用微信推送接口向用户推送该新闻,本用例结束。如果匹配状态为“不匹配”,用户将不会收到该新闻的推送,本用例结束。无特殊要求。前置条件:本用例开始前采集到的新闻必须有效。后置条件:如果用例成功,用户将收到一条新闻推送。活动图

    第四章 新闻采集与订阅系统的设计4.1 系统架构及原理本新闻采集与订阅系统分别由爬虫部分与新闻订阅和展示部分构成,在新闻订阅与展示部分采用基于C’s的架构,代码的组织方式为MVC三层结构,其中的三个层次分别为视图层(View)、控制器层(Controller)和模型层(Model)。代码整体采取前后端分离的方式,前端负责视图层,后端负责模型层和控制器层,客户端使用微信和网页实现,前后端通讯使用AJAX交换JSON的方式。系统的总体框架图如图4.1所示:

    爬虫部分使用了Python编写Scrapy框架,它的基本架构如图4.2所示,其中 Scrapy引擎的作用是控制数据的流向,是整个爬虫框架的核心。网络蜘蛛(spiders) 定义了如何爬取某个(或某些)网站,包括了爬取的动作以及如何从网页的内容中提取结构化数据。蜘蛛中间件(spider middleware)是在Scrapy引擎和网络蜘蛛间的一个钩子,它可以处理蜘蛛的输入与输出。调度器(scheduler)能够从Scrapy引擎接受请求并放入队列,在引擎请求调度器时返回对应的请求。下载器(downloader)负责下载网页,把爬取到的内容返回给Scrapy引擎和网络蜘蛛。下载器中间件(downloader middleware)是在Scrapy引擎和下载器间的一个钩子,它可以处理传入的请求跟传出的响应。Item Pipeline负责处理网络蜘蛛传过来的Item,可以在此做数据格式化,数据清理等操作。

    爬虫的整体上数据流向的开始是由Scrapy引擎让网络蜘蛛以一个初始的 URL来初始化一个请求,并设置回调函数,然后网络蜘蛛把该请求向调度器申请任务,把申请到的任务交给下载器,这里会经过一次下载器中间件,然后下载器把下载完后产生的响应再经过一次下载器中间件,然后传递给引擎,引擎接收到该响应后通过蜘蛛中间件传给网络蜘蛛处理,网络蜘蛛处理该响应,产生一个 item或者是新的请求给引擎,引擎会把传过来的item放入Item Pipeline,把传过来的新的请求传给调度器,Item Pipeline获取接收到的item对该item进行逐层处理,接着这个流程就重复直到爬取完成。
    4.2 系统模块设计4.2.1 爬虫采集模块设计使用Scrapy框架来编写爬虫首先要编写核心的蜘蛛(sPiders)的代码,Spider 类定义了如何爬取某个(或某些)网站,包括了爬取的动作以及如何从网页的内容中提取结构化数据。本系统主要针对网易新闻和腾讯新闻的科技频道进行主题式的爬虫,所以设计了两个网络蜘蛛,名字分别为Neteasespider和QQSpider,如果后续需要更多需要爬虫的站点,只要增加对应站点的网络蜘蛛就可以了,其余处理部分都是通用的。在这里选择主题式的爬虫的原因主要是一个通用爬虫对于新闻这样的每个站点的文章有固定格式的爬取解析的代价比较大,还不如手工去对需要爬取的站点进行分析,根据每个站点的特点来编写解析的代码。这样的主题式的爬虫能够提高爬虫的精确度,同时也提高了爬虫的效率,这样我们的爬虫就能够及时爬取到最新的新闻内容了。每个站点对应一个特定的网络蜘蛛还有一个好处就是如果后续需要完成分布式爬虫等需求时会很方便,因为这样的方式代码之间的祸合度较小,同时非常简洁。
    网络蜘蛛首先从一个Start url开始爬取,这里我选取了网易新闻和腾讯新闻的科技频道的首页,蜘蛛爬取这个起始URL上的页面后,对里面的内容进行解析。因为每篇新闻的URL都具有一定的格式,凡是该页面上有符合这种格式的 URL,蜘蛛都会对这些URL进行回调,继续爬取这些URL的页面,这些页面上就会包含所要获取的新闻的内容了。对于同一个新闻站点来说,一般页面上的内容的结构也是一样的,所以按照一定的规则来对这些页面上的内容进行解析,获得新闻内容原始数据,对这些数据进行格式化的处理,封装成一个item,传回给 Scrapy引擎处理。
    因为新闻具有一定时效性,一般来说我们只会关注那些新产生的新闻内容,所以本爬虫不需要考虑需要爬取过往产生的新闻的情况。本系统的爬虫部分只聚焦于每个站点的首页的新闻,因为新闻是滚动刷新的,所以我们需要定时对首页进行爬取,获取新的新闻内容。本系统设计了一个类似守护程序,来控制爬虫的启动与停止,在爬虫结束后等待一段时间再重新开始爬取。
    4.2.2 爬虫去重模块设计在爬虫过程中会遇到重复内容的情况,所以我们需要设计一个爬虫用到的去重的模块。考虑到每个URL对应的新闻内容是不变的,我们只要针对URL来进行去重即可,而不需要等到把内容取回来之后再判断内容是否已经爬取过,那样会消耗大量额外的资源,也对目标网站造成了额外的压力,显得不友好。我们选择去重的时机是Scrapy的调度器把请求分配给下载器之前,也就是说在下载器中间件中处理,在本系统中定义了一个下载器中间件RedisMiddleware,这个中间件的作用是在Redis的一个散列中判断是否存在该URL,如果不存在,把该请求传给下一个中间件处理。如果该URL存在于散列中,则忽视掉该请求,不进行后续操作。在本系统中定义了一个Item管道RedisPipeline,在爬取数据完成后,数据库处理完后Item会传到该管道,该管道的作用是把这个新闻所属URL 存入Redis的散列中,标记该URL已经爬取。
    4.2.3 防反爬虫模块设计防反爬虫是在大多数爬虫中需要考虑的情况,因为爬虫对网站服务器造成的压力比正常人要多,如果爬取频率足够高的话,会使网站访问变慢,甚至无法访问,所以网站可能会有一系列的反爬虫措施。首先我们的爬虫需要遵守网站的爬虫协议,然后把爬取速率控制好,例如间隔一秒才爬取一个页面。其次,我们需要伪装成一个浏览器,有些网站会通过HTTP请求头中的User-Agent中的信息来判断用户,我们不但需要在爬虫请求中的HTTP设置User-Agent请求头,还需要对该请求头进行更换,因此在本系统中定义了一个下载器中间件RotateUserAgentMiddleware,这个中间件的作用是在请求前在请求的HTTP请求头中设置一个轮换的随机的模拟用户浏览器的User-Agent请求头,这些 User-Agent与真实浏览器的User-Agent一致,数据来源是Python中一个叫 fake-useragent的库。后续如果对方服务器针对’P进行禁封了的话可以采用代理服务器的方式来应对,在做了以上措施的情况下本系统目前没有出现过被禁封的情况,因此该方法没有在本系统中实现。
    4.2.4 爬虫存储模块设计爬虫的数据存储是一个爬虫系统中很重要的一部分,因为爬虫的目的就是获得数据,在这里我们需要考虑数据的存储方式与储存时机。在本系统中储存部分使用了ORM(对象关系映射)的方式来实现,ORM的好处在于把数据访问的细节隐藏起来,在ORM上的操作出错的可能性会比手写数据库操作的可能性低。在ORM中,我们只需要关注数据的结构,这样一来,我们只需要编写数据储存对象的参数定义等属性跟方法就可以了,初始化、查询、更新等操作都可以由 ORM来实现。在本系统中,爬虫部分与订阅和展示部分都共用一个数据库,爬虫部分需要对数据库进行写操作,展示部分需要对数据库进行读操作。在蜘蛛解析完数据后,蜘蛛会把封装好的Item通过Scrapy引擎传给Item管道,本系统中定义了一个MongoDBPipeline,这个管道的作用是维持一个MongoDB的数据库链接,接收到传入的Item后先校验完数据的完整性,然后把合法的数据插入数据库对应的集合中,否则丢弃该Item。
    4.2.5 消息推送模块设计消息推送部分本系统使用微信来实现,需要用户关注指定公众号。本系统需要推送消息给用户时,先选择一个本系统预定义的模板,在模板中填入消息标题,内容和链接等数据后,通过微信提供的接口来进行推送。这里需要注意的是我们需要给推送的消息接口提供一个本系统所用的公众号的AccessToken,这个 AccessToken是向微信证明本系统的凭证,它有一定的有效期,需要定时刷新。
    4.2.6 消息订阅与展示模块设计消息订阅与展示模块是本系统中与用户交互的模块,这个模块负责用户订阅新闻的功能与向用户展示所需新闻内容的模块。在本系统蜘蛛解析完数据后,蜘蛛会把封装好的Item传给MongoDBPipeline储存后,会继续往下传递,传递到一个PushPipeline中,这个管道的作用是判断爬取到的数据是否包含用户所订阅的关键词,如果包含的话则调用消息推送模块把新闻消息推送给用户。在消息推送后,用户会在微信端本系统的公众号中接收到一条包含新闻消息简要内容的消息,点击该消息可以跳转到新闻展示页面。本系统提供了一个消息订阅页面,用户可以在该页面上管理自己的新闻关键词。
    4.3 数据库设计本系统存放数据用到的数据库分别是Redis和MongoDB,在本系统的数据库设计中,数据库的集合主要包括爬取到的新闻信息集合和用户订阅新闻关键词集合,系统的配置信息都写在配置文件中,就不需要使用数据库来存放了。这里选择MongoDB的原因是考虑到当爬虫的数据量和并发数很大时,关系型数据库的容量与读写能力会是瓶颈,另一方面,爬虫需要保存的内容之间一般不会存在关系。另外本系统会使用Redis中的散列类型来存放已经爬取过的URL和不合法的URL,因为判断URL是否合法或者是否已经爬取过是一个高频的操作,使用 Redis这样的高性能的内存键值对类型的数据库可以减少主数据库的压力,同时提高爬虫的性能。
    新闻信息集合



    属性名
    含义
    类型
    说明




    title
    新闻标题
    string



    content
    正文内容
    string
    纯文本


    source
    来源
    string
    新闻出处


    published
    发布时间
    timestamp
    精确到秒


    url
    原文链接
    string
    用于跳转



    用户订阅新闻关键词集合



    属性名
    含义
    类型
    说明




    open_id
    用户微信openid
    string
    唯一标识


    keywords
    订阅的关键词列表
    array
    字符串类型的数组


    tags
    订阅的标签列表
    array
    字符串类型的数组



    第五章 新闻采集与订阅系统的实现5.1 系统框架实现本新闻采集与订阅系统的爬虫部分框架是利用Scrapy自带的命令行工具来初始化,初始化后已经创建好了Scrapy引擎所需的几个重要的文件,如中间件,数据管道,配置文件等,这样做的好处是能够快速搭建起框架,并且能够达到官方定义的最佳实践。接下来我们可以在这个目录下定义自己的一些模块文件,再在这些文件中实现自己的处理函数就可以了,最终实现的爬虫部分的目录结构如图5.1所示,其中items.py是用于定义数据储存模型的文件,middlewares.py是用于定义中间件的文件,pipelines.py是用于定义数据管道的文件,settings.py是本系统爬虫部分的配置内容,spiders文件夹中存放了不同爬虫的网络蜘蛛代码, utils.py则是一些通用的函数存放的地方,wechat_config.py和wechatpush.py分别是微信推送部分的配置和推送代码。

    新闻订阅和展示部分的API服务器端则使用Django自带的命令行工具来初始化,使用django-admin startproject命令来新建一个项目,然后使用django-admin startapp命令来新建一个app,这样API服务器的基本框架就完成了,然后往创 建的目录中添加其余代码,最终实现的新闻订阅与展示部分的目录结构如图5.2 所示,其中frontend文件夹存放的是本系统的前端静态文件,分别是新闻订阅页面和新闻展示页面,init_db.py文件是一个用于初始化数据库用的脚本,lib文件夹中存放的是本系统中一些能够被公用的函数文件。manage.py是由Django生成的用于管理任务的命令行工具脚本,newsweb存放的是本项目的代码,run server.sh是一个用于启动服务器的脚本文件,web server中存放的是本系统新闻订阅与展示部分的服务端代码的主要文件,主要包括了用于配置路由 urls.py,存放新闻和订阅信息数据模型models.py和提供API的views.py。

    5.2 爬虫采集模块实现爬虫采集模块的核心的网络蜘蛛,下面以爬取网易科技频道新闻的蜘蛛为例讲解本系统爬虫采集模块的实现过程。图5.3为该蜘蛛的解析网页请求响应的代码,首选我通过分析网易科技频道新闻中的网页源码,分析得到网页中所需的新闻内容的数据所在的位置特征信息,例如通过分析发现标题位置是处于html标签下的head标签里的title标签里的文本。24-27行中的代码的作用是通过xpath 使用之前分析出来的格式来从抓取到的数据中提取出新闻相关的信息,包括新闻标题、新闻消息来源、新闻内容、新闻发布时间。29-32行的代码作用是把时间解析为时间戳,这样做的目的是为了方便把时间转换成不同的表现格式,时间表现会更为准确。34-40行的代码作用则是把数据封装成一个本系统中的新闻Item, 然后传给Item管道来处理。另外一个用于爬取腾讯新闻科技频道蜘蛛的分析方法和代码写法是类似的,在这就不详细介绍了。

    为了实现定时爬虫的功能,在本系统中实现了一个名为worker.py的守护进程脚本和一个start_crawl.py的用于调用爬虫的脚本,运行worker.py脚本后会每三十秒调用启动一次start_crawl.py,start_crawl.py每次启动会调用爬虫主程序,程序核心代码如下:

    5.3 防反爬虫模块实现为了防止反爬虫对本系统爬虫部分的影响,对于每次请求,本系统都会伪装成一个真实的用户,防止被爬取的网站通过User-Agent等信息来判断或者禁封 掉本系统的爬虫,导致后续爬虫无法正常进行。本系统在发送请求之前会在请求的头部加上User-Agent的请求头信息,这个请求头的信息会在本系统配置中的 User-Agent列表中随机选取一个,图5.5为部分User-Agent信息。除此之外,还可以利用代理服务器来代理请求,防止被爬取的网站通过IP信息来禁封本系统爬虫。

    5.4 爬虫存储模块实现爬虫储存模块的数据设计与格式等在上一章已经说明,在这介绍在数据库中的具体实现。爬虫爬取到的新闻数据会存放于MongoDB中,使用ORM来映射数据对象模型到数据库,使用的ORM框架是MongoEngine,下面通过讲解一个新闻内容的数据模型的定义来说明这种定义方式,在图5.6中的第9行,我们定义了一个父类为MongoEngine的Document类的类,这样定义就使这个类拥有了关系对象映射的能力,再在这个类中定义一个to_json的方法,作用是把本类的实例转化为一个Dict类型的数据,方便API调用时将对象转换成JSON格式的数据返回给前端。图5.7展示了部分爬取到的新闻数据的内容。

    5.5 消息推送模块实现消息推送模块使用了微信公众号的推送,在本系统中使用微信的接口测试号来代替公众号,微信的接口测试号是一种用于测试的,可以使用微信号扫一扫登录的账号,而且这种账号能够直接体验和测试公众平台所有高级接口。在申请完后登录系统,获得该系统的applD和appsecret,这两个字符串是使用该账号的凭据。需要注意的是,用户需要关注本账号后才能够收到本账号推送的消息。

    接下来我们在网页下方新增一个消息模板,填入推送新闻消息的模板内容,填写完成后记录对应的模板ID。

    获得以上信息后把信息写入消息推送模块的配置文件中,供消息推送模块调用。下面讲解消息推送模块核心部分的实现,核心部分如图5.10所示,是一个名为send_msg的函数,这个函数接收四个参数,分别为新闻标题、新闻内容、新闻的ID和订阅者的openid。订阅者的openid是用户微信的唯一标识,在测试号的页面可以查看已关注该账号的用户微信的openid。该函数的29-42行的作用是把数据封装成微信推送接口所需的格式,然后在45行使用requests模块来 POST一个请求到微信推送接口,微信推送接口收到请求后会在公众号中把该消息推送给用户。该函数使用了一个自定义的装饰器update_token来装饰,之前存放的applD和appsecret可以用来生成推送用的access token,而这个access token 有固定的存活期限的,这个装饰器的作用就是定时去获取这个access token并存放,直到过期之后再重新获取。
    爬虫模块爬取到含有用户订阅的关键词的新闻时会向该用户推送这则新闻,图5.11是用户在微信公众号上收到的该新闻的推送消息示例。

    5.6 消息订阅与展示模块实现消息订阅与展示模块主要由前端静态文件部分和后端API部分组成。在开发方式上本系统选择了使用前后端分离的方式,前端通过AJAX的方式来跟后端提供的API进行交互,后端API服务器收到请求后返回对应的JSON格式的数据给前端,前端根据数据来渲染出最终展示给用户的页面,这种前后端分离的方式有效地降低了代码之间的Wi合度。在前端实现方面,使用了jquery来对DOM元素进行操作以及进行异步请求等,另外使用了WeUI的样式库,WeUI是一套提供同微信原生一致的视觉体验的基础样式库,由微信官方设计团队为微信内网页和微信小程序量身设计,令用户的使用感知更加统一。
    接下来以用户端的角度来展示消息订阅与展示模块的实现。
    用户想要收到新闻推送,需要先关注本系统的公众号,然后打开新闻订阅页面:

    这时可以输入要订阅的关键词,这里填IT,点击添加订阅关键词,系统提示添加关键词IT成功,刷新已订阅关键词列表:

    点击已订阅关键词列表中的项会弹出对话框询问是否删除该关键词:

    点击确定,提示操作成功,同时刷新已订阅关键词列表:

    在订阅关键词后,系统爬虫爬取到相关内容时会把内容通过微信推送给用户,用户点击后可以看到新闻内容,在此页面可以点击查看原文按钮打开原新闻页面,还有可以点击订阅更多前往订阅新闻关键词的页面:

    点击查看原文,会跳转到新闻的原页面:

    第六章 系统部署6.1 部署机器概述为了运行本新闻采集与订阅系统,至少需要一台拥有公网’P的Li~服务器,这是为了用户在外网能够访问到。至于配置方面则不需要太高,在测试时我选用了一台腾讯云上的服务器,这台服务器的配置如下:



    项目
    内容




    操作系统
    Ubuntu Server 14.04.1 LTS 64位


    CPU
    1核


    内存
    1GB


    系统盘
    20GB


    公网带宽
    1Mbps



    6.2 配置环境
    安装Nginx作为反向代理服务器,并编辑Nginx相关配置文件,这样做是为了把不同的请求分发到后端不同的地方,例如请求前端文件就返回静态文件,请求API就把请求转发给API服务器,Ningx的配置文件部分内容如图6.1所示。这样就实现前后端分离而又不受到跨域请求限制的影响了。编辑完Nginx配置文件后,重启Nginx服务器。
    安装PIP, PIP是Python用于管理第三方库的一个软件,这里用于安装本系统所需的第三方库。
    使用PIP安装本系统所依赖的第三方库,包括pymongo, scrapy, redis, fake-useragent, django,mongoengine, jieba, lxml, gevent, gunicorn等。


    6.3 系统运行由于本新闻采集与订阅系统是由爬虫部分与展示部分组成,所以需要分别运行行爬虫的守护进程和后端API服务器,静态页面是由Nginx指定的一个目录来提供的,不需要后台服务器。
    使用python worker.py命令来运行爬虫的守护进程,得出以下输出:

    使用sh run_server.sh命令来运行后端API服务器,这个脚本的实际作用是使用gevent作为gunicorn的worker来运行4个后端API服务器进程,成功运行会得到以下输出:

    第七章 总结与展望7.1 总结本系统是一个基于网络爬虫实现的新闻采集与订阅系统,实现了对网络上新闻内容的自动化采集、用户新闻关键词订阅、新闻内容展示以及新闻推送等功能。为实现本系统的功能,查阅了大量学习资料,在实现方面使用了一些比较前沿的技术以及较多的第三方库,从中能够学习到很多新知识和新技能。在本文中较为完整地从系统的需求分析、不同模块的设计与实现几个方面来展示了一个完整的爬虫系统以及对应的新闻订阅API服务器等的实现过程,最后在云服务器上部署本系统并测试,达到了预期的效果。
    7.2 展望本新闻采集与订阅系统在设计上考虑了许多来降低代码之间的祸合度,同时提高代码的健壮性与性能,使本系统能够达到容易扩展以及高可用的需求,即便后续需要爬取另外一个新的新闻网站上的新闻,只需要编写对应网站的解析部分就可以了,大部分代码已经被模块化,能够被重用。对比了已有的类似的成熟大型新闻服务系统,发现还有以下能够改进的地方:

    本系统只对新闻的基本文字信息等进行了采集与展示,后续可以考虑实现对新闻中图片与视频等多媒体信息的采集。
    本系统缺乏一个较为完善的用户模块,目前用户是在配置文件中配置的,用户模块对于这类的订阅系统是比较重要的。
    订阅机制不够智能,也没有智能推荐等功能,后期可以采用机器学习等人工智能方法来实现智能化推送与推荐功能。

    除了以上几点,本系统仍然存在许多能够改进的地方,但由于本文作者水平有限以及时间限制,未能够将这些一一实现,还希望各位专家学者能够给予批评与建议。
    参考文献[1] 中国互联网络信息中心. 中国互联网络发展状况统计报告[EB/OL]. http://www.cnnic.cn/gywm/xwzx/rdxw/20172017/201701/t20170122_66448.htm, 2017年
    [2] 胡博,基于网络爬虫的内容资源评价研究[D];北京理工大学;2015
    [3] 李建中、李金宝、石胜飞,传感器网络及其数据管理的概念、问题与进展,软件学报,14(10):17 17-1727, 2003
    [4] 邝洪胜;基于Python的电商导购APP设计与实现[D];华南理工大学;2015
    [5] 基于Django的自动化运维管理系统的设计与实现[D].姚娜.西安电子科技大学2015
    [6] 关系与非关系数据库应用对比研究——以SQL Server与Mongo DB为例[D].吴德宝.东华理工大学2015
    [7] 基于网络爬虫的网站信息采集技术研究[D]. 孙骏雄. 大连海事大学2014
    [8] 基于网络爬虫的内容资源评价研究[D].胡博.北京理工大学2015
    [9] Wang J, Guo Y. Scrapy-based crawling anduser-behavior characteristics analysis on Taobao[C1//Cyber-EnabledDistributed Computing and Knowledge Discovery (CyberC), 2012 International Conference on. IEEE, 2012:44-52.
    [10] Castillo C. Effective web crawling[C1//Acm sigir forum. Acm, 2005, 39(1):55-56.
    [11]刘金红,陆余良.主题网络爬虫研究综述 [J].计算机应用研究,2007, 24(10) :26-29.
    [12]徐远超,刘江华,刘丽珍,等.基于Web的网络爬虫的设计与实现[J].微计算机信息,2007 (21): 119-121. MLA
    [13] 王成军.“今日头条”的技术逻辑:网络爬虫+矩阵筛选[J].传媒评论,2015 (10) : 34-37.MLA
    [14] Jaiswal S, Kumar R. Learning Django Web Development [M]. PacktPublishing Ltd, 2015.
    [15] Taneja 5, Gupta P R. Python as a Toolfor Web Server Application Development [J]. 2014.
    [16] Web数据挖掘及其在网络新闻文本数据中的应用[D].胡峰.电子科技大学2010
    [17] 乔峰.基于模板化网络爬虫技术的Web网页信息抽取[D].电子科技大学2012
    致谢从本论文的选题、资料收集、资料阅读、到论文编写完成的这段时间中,我收获到许多宝贵的知识与经验。在这段时间里我查阅了许多相关资料,也使用了一些他人开发的第三方程序库,这些都减轻了我的压力。在此要感谢前人的付出,留下了那么多的学习研究材料。
    在这我要特别感谢我的论文导师卞静老师,卞老师在我完成该论文期间给予了我悉心指导与帮助,在繁忙之中抽出时间来对我的论文进行了指导并提出许多宝贵的建议。卞老师拥有渊博的专业知识、严谨的治学态度和平易近人的处事作风,是我终生学习的楷模。在此向我的导师表示最诚挚的谢意和最衷心的祝愿。
    其次,我要感谢我的家人,他们一直以来都能够理解,支持并关心我,从而使我能够专心投入到学习和工作中,让我在求学的过程中感受到温暖的力量,并能够顺利完成学业。
    在我大学学习和生活中,得到了许多老师和同学的关心与帮助。感谢我的舍友,在我学习上遇到疑惑的时候能够悉心教导我,在论文编写、技术路线和具体实现上也得到了他们宝贵的建议,谢谢你们!
    最后,在此对所有在我做毕业设计期间帮助,关心和支持过我的老师、同学和朋友们,以及百忙之中抽出时间来审阅、评议本论文的各位专家们表示衷心的感谢。
    2 评论 28 下载 2018-09-30 23:27:17 下载需要12点积分
显示 0 到 15 ,共 15 条
eject