毕 业 设 计
题目: 基于云存储的图像管理平台设计与实现
学 生: 王骏
学 号: 201606060129
学 院:电子信息与人工智能学院
专 业: 计算机科学与技术
指导教师: 齐勇
2020年 6 月 30 日
毕业设计(论文)任务书
电子信息与人工智能 学院 计算机科学与技术 专业 163班级 学生:王骏
题目: 基于云存储的图像管理平台设计与实现
毕业设计(论文)从 2020 年 2 月 24 日起到 2020 年 6 月 15 日
课题的意义及培养目标:
在本本次毕业设计中,将会综合运用本科所学计算机科学与技术专业相关知识,完成实际的应用开发。
传统的云存储系统采用集中的存储服务器存放所有数据,存储服务器成为系统性能的瓶颈,也是可靠性和安全性的焦点,不能满足大规模存储应用的需要。
分布式网络存储系统采用可扩展的系统结构,利用多台存储服务器分担存储负荷,利用位置服务器定位存储信息,它不但提高了系统的可靠性、可用性和存取效率,还易于扩展。因此,本项目利用多台服务器的存储资源来弥补传统云存储系统的不足,通过Nginx服务器作为负载均衡服务实现高负载的分布式云存储方案。
设计(论文)所需收集的原始数据与资料:
C++相关的开发软件工具,Qt客户端设计工具,Nginx服务器,Linux操作系统,MySQL数据库,Redis数据库,分布式文件系统,项目所需的图像数据,分布式文件系统相关资料。
课题的主要任务(需附有技术指标分析):
本次设计主要为高负载高并发基于分布式的图像管理存储平台。主要实现用户注册,用户登陆,图像文件列表获取,文件上传,下载,分享,删除等基本功能。
本次设计采用C/S架构,分为客户端和服务端,客户端采用Qt进行界面设计。服务端的基本流程为,客户端请求经过反向代理后通过HTTP服务器请求进行相应分布式文件系统管理及数据库管理。以上传为例,首先客户端的请求通过Nginx反向迭代处理作为负载均衡方案,之后通过对应服务器请求先进行数据库信息校验,当该上传数据不存在时,通过追踪器将图像数据存储到相应的分布式文件系统中,再将存储信息存储至数据库以及缓存数据库中。
预计:并发量同步请求初步预计500人左右。本地测试上传速度:30MB/S ~ 50MB/S 客户端下载速度: 20MB/S ~ 40MB/S 服务器中上传速度: 5MB/S ~ 7MB/S 客户端下载速度: 0.1MB/S~ 0.15MB/S
设计(论文)进度安排及完成的相关任务(以教学周为单位):
周 次 | 设计(论文)任务及要求 |
---|---|
1-3 | 相关资料的查阅与学习 |
4-5 | 整个方案的初步设计 |
6-10 | 代码的设计与实现 |
11-12 | 测试与bug修复 |
13-14 | 论文的梳理 |
15-16 | 论文的完善 |
学生签名:
指导教师:
教研室主任:
基于云存储的图像管理平台设计与实现
随着计算机与网络的发展,国内外的图像存储已逐渐实现由传统图像向数字化图像转型。这意味着图像越来越多地以二进制信息保存在计算机中,这样不仅可以长期保存,而且还有方便复制、随意修改、便于传输等优点。而随着大数据与云计算的发展,图像的存储逐渐也向分布式存储方向发展,使得图像管理的物理存储资源不一定直接连接在本地节点上,而是通过计算机网络与节点相连。分布式文件系统的设计基于C/S模式。而且分布式文件系统也轻松解决了海量图像处理的难题。
利用大数据和云计算的优势,以云存储的方式进行图像数字化存储管理,可以解决常规对象存储所常见的海量图像存储效率低下且不安全、服务器无法承载多用户并发访问等问题。因此设计一套基于分布式文件系统的云存储图像管理平台,为用户提供高效且安全的图像上传、下载、分享、预览等对象存储与管理服务,解决常规物理存储所带来的数据易丢失难恢复、下载存储不方便、存储空间有限、需随身携带等问题,同时解决普通存储网盘用户并发量低下、数据共享性差等问题。
平台采用C/S模型,即客户端/服务端模式。客户端通过Qt可视化设计工具设计用户界面与文件管理界面,客户端语言通过使用Qt类库完成客户端逻辑。服务端框架首先采用Nginx服务器进行反向代理,解决负载均衡与高并发问题;数据对象存储通过分布式文件系统FastDFS进行数据存储;数据库通过持久化数据库MySQL与缓存数据库Redis的协调工作进行数据信息管理;服务端语言采用以C++语言为主的CGI程序完成服务端逻辑。
基于云存储的图像管理平台,最终为用户提供高效且安全的图像云存储与管理服务,包括用户的登录与注册、数据上传、数据下载、文件列表浏览、共享文件浏览、下载榜浏览、数据传输情况浏览、文件的共享与转存、数据删除、图像预览等功能,可作为图床进行图像存储,也可作为云盘进行对象存储,方便用户对图像数据进行数字化管理。
关键词:云存储,分布式文件系统,Nginx,MySQL,C++,Qt
Design and I**mplementation of I**mage M**anagement P**latform based on C**loud S**torage
With the development of computer and network, image storage at home and abroad has gradually realized the transformation from traditional image to digital image. This means that the image is more and more stored in the computer with binary information, which not only can be saved for a long time, but also has the advantages of convenient replication, arbitrary modification, easy transmission and so on. With the development of big data and cloud computing, the storage of images is also gradually developing towards distributed storage. The design of distributed file system is based on C / S mode. Moreover, the distributed file system can easily solve the problem of massive image processing.
By using the advantages of big data and cloud computing, digital image storage management in the way of cloud storage can solve the common problems of mass image storage, such as low efficiency and insecurity, the server can not carry multi-user concurrent access and so on. Therefore, a set of cloud storage image management platform based on the distributed file system is designed to provide users with efficient and safe image upload, download, share, preview and other object storage and management services, to solve the problems brought by the conventional physical storage, such as easy loss and hard recovery of data, inconvenient Download and storage, limited storage space, need to carry around, and to solve the common storage network disk There are many problems such as low concurrent users and poor data sharing.
The platform adopts C / S model, namely client / server mode. The client uses QT visual design tool to design user interface and file management interface, and the client language uses QT class library to complete the client logic. The server framework first uses nginx server as the reverse proxy to solve the problem of load balance and high concurrency; the data object storage uses fastdfs as the distributed file system for data storage; the database uses MySQL as the persistent database to coordinate with redis as the cache database for data information management; the server language uses CGI program based on C + + to complete the server logic.
The image management platform based on cloud storage provides users with efficient and safe image cloud storage and management services, including user login and registration, data upload, data download, file list browsing, shared file browsing, download list browsing, data transmission browsing, file sharing and transfer, data deletion, image preview and other functions, which can be used as a drawing bed Image storage can also be used as cloud disk for object storage, which is convenient for users to digitize image data management.
Key words: Cloud Storage, Distributed File System, Nginx, MySQL, C + +, QT
目 录
5.3.4 文件查询模块(文件列表、共享列表、下载榜) 41
由于大数据与云计算技术的普及,互联网已经与人们的生活越来越密切,人们无时无刻不产生着各种数据。同时伴随着直播、短视频、电子相册、数字影院等流媒体的发展,图像数据处理需求正急速增长,传统的物理存储由于共享性差、数据处理缓慢、不易于迁移与恢复等缺点,已渐渐不适用于业务发展需求。而随着云计算的发展,分布式存储的大容量存储、方便移植、快速存储、快速共享、快速备份等各种优点逐渐显现出来,图像存储已渐渐从传统的物理存储方式转变为云存储方式。
基于云存储的图像管理方式是大数据时代数据对象存储的重要方式之一,这种对象存储方式不仅应用于图像管理,更应用于大数据时代影视、文件、用户信息等各种数据存储之上。通过云存储的方式,可对数据随时上传下载,随时分享备份,使数据对象的管理更加方便方便,更加安全。项目尝试采用分布式文件系统对数据对象进行存储,同时采用Nginx服务器实现高并发与负载均衡,对海量用户的请求进行响应,为用户提供可靠、方便的图像管理平台。
随着计算机与网络的发展,国内外的图像存储已逐渐实现由传统图像向数字化图像转型。这意味着图像越来越多地以二进制信息保存在计算机中,这样不仅可以长期保存,而且还有方便复制、随意修改、便于传输等优点。而随着大数据与云计算的发展,图像的存储方式逐渐向分布式存储方向发展,使得图像管理的物理存储资源不一定直接连接在本地服务器结点上,而是通过网络与存储结点连接[1],因此衍生出了分布式文件系统。分布式文件系统即集群文件系统,是指物理存储资源通过网络与结点相连,支持大数量的节点以及PB级的数量存储[2]。因此,分布式文件系统解决了海量图像处理相关的难题。
国内自从2008到2009年间,以华为为代表的一些国内互联网企业开始踏足个人云存储业务,开发出国内第一批网盘产品。2010年以后,出于占领大数据时代入口的目的,国内互联网巨头也纷纷布局云存储业务,腾讯、百度、360、金山、华为等互联网企业也陆续推出了自己的云盘,并开始跑马圈地[3]。这意味着大数据不仅为国内开拓了广阔的市场,也使得国内已逐渐由传统存储向云存储转型[4]。而自从阿里云、腾讯云纷纷崛起,不仅推动了云计算在国内的发展,也在客观上对国内的市场进行了开拓,对云服务提供商们形成了刺激作用。2018年我国数字经济规模达到31.3万亿元,占当年GDP比重达34.8%[5]。
进入5G时代,5G将与AI智能、IoT物联网、Cloud云、Big Data大数据、Edge Cloud边缘云等技术交叉发展,共同构建智能化的基础设施。云已成为新型信息基础设施的重要组成部分,也是面向政企提供数字化服务的主入口,各行各业的数字化发展已经离不开云计算的支撑。中国云市场IaaS、PaaS、SaaS服务模式继续协同发展,市场规模随客户需求的变化而调整[6]。
国外云存储市场2017年为307亿美元,预计到2022年889.1亿美元,CAGR为23.7%[7]。中国云存储市场2017年规模为88.68亿人民币,同比增长71.8%,2018年同比增长率将上升至72.8%[8],市场规模为158.5亿元人民币。Gartner在2019年的报告中显示:到2024年,40%的企业将实施至少一种混合云存储方式,高于2019年的10%[9]。存储如何更好的服务私有云,成为企业级存储的一个重要课题。除了存储自身的池化、自动化之外,向上提供API,方便私有云管理平台按需驱动存储资源的创建、调整、优化甚至回收,将逐渐成为必备配置。云化对接包含两大块,一是对接开源的云管理平台;二是对接商业的云管理平台,首当其冲的是能够被主流Hypervisor识别,如VMware ESXi、Microsoft Hyper-V[10]。
如今数据处理需求飞速增长,稍不留神便会跟不上时代发展的步伐。传统的数据管理技术已逐渐不能满足互联网应用所提出的对大数据管理的要求。如何高效地存储和管理海量图像数据,已然成为当今时代的热门探讨话题。
而云存储、云服务、虚拟化等IT热词的不断盛行,已标志着云计算正引领着时代发展的趋势。相较于传统的物理存储,基于云存储的图像管理方式不仅解决了物理硬件易损坏、数据易丢失的问题,而且存储容量可弹性扩展,这意味着基于云存储的图像管理方式不仅扩容方便,而且在存储更新或者服务升级的过程中并不会引起服务中断,造成不必要的损失。
本着开源精神,借鉴传统数据存储与管理的方式,针对大数据与云计算的特点,设计了一套基于云存储的图像管理平台,方便管理图像数据。该平台采用分布式文件系统、缓存数据库、负载均衡等技术,具备弹性伸缩、弹性扩容、高并发等特点,支持海量图像数据的存储与管理。实践证明,该平台具备可行性,可满足空间信息服务的多种需求。
平台设计使用的服务器是基于FastDFS分布式文件系统以及Nginx服务器作为反向代理的Linux服务器,编程语言使用C++语言,图像存储于FastDFS中,数据信息存储于持久性数据库MySQL,Redis作为辅助提高访问效率。客户端使用Qt作为开发工具设计该平台。该平台客户端界面美观方便,功能齐全。数据采用Md5加密算法与Base64数据处理技术,保证数据传输的安全性[11]。该平台是一个简单实用的图像管理平台,具有以下功能:
用户注册与登录,文件的上传与下载,查询与删除,共享与转存。同时针对图像文件具备预览功能。
该平台重在为用户提供一套完整的图像管理与存储服务,解决常规物理存储所带来的数据易丢失难恢复、下载存储不方便、存储空间有限、需随身携带等问题,同时解决普通存储网盘用户并发量低下、数据共享性差等问题。
传统的云存储方式将数据直接集中存储于某一台或多台服务器或者硬盘上,这样虽然方便服务端的查询,但是一旦服务器或硬盘损坏,便会造成数据丢失且无法恢复。而且由于数据无法直接共享,导致资源的浪费。如今随着时代的发展,无论软件还是硬件的更新速度越来越快,传统的存储方式由于更新麻烦,数据迁移或备份困难,因此已渐渐被分布式存储所替代。因此,对于服务端,应该满足以下需求:
(1) 功能需求
(a) 为用户提供云存储服务。
(b) 满足用户完备的图像管理功能需求。
(c) 对于用户的数据管理应具备快速响应。
(d) 针对多人访问服务器的情况应具备负载均衡的能力。
(e) 数据应具备快速备份与易迁移的特性,保证用户数据不会因服务器问题而丢失。
(2) 性能需求
(a) 多人同时访问服务器应具备高并发性。
(b) 并发不会带来内存泄露等安全问题。
(c) 对于高频数据访问应快速响应,而低频数据应不过多占用内存资源。
(d) 对于用户的信息例如密码等敏感信息应保证安全性。
(e) 程序执行流程合理,代码思路清晰,不会造成逻辑上的问题。
(3) 可靠、可用性需求
(a) 针对各类错误应有相应的提示或解决方案。
(b) 对于失败操作需要有一套完整的错误码进行失败原因提示与排查。
客户端作为用户一系列操作的载体,对用户的使用体验起着最直接的决定作用。因此,客户端不仅需要具备齐全的功能服务,还需具备美观、操作简单、处理流畅等特性,这样才能避免用户的流失。因此,将客户端独立出来进行需求分析。以下从业务需求与性能需求两部分进行分析:
(1) 业务需求
(a) 为用户提供用户身份的载体,因此需包含用户登录与注册界面。
(b) 作为平台的核心,需具备上传图像与下载图像的功能按钮。
(c) 方便数据的交互,因此需具备共享文件的浏览界面。
(d) 提高用户数据获取的速度,为用户提供数据下载量的界面。
(e) 为节约用户空间,需为用户提供图像预览的功能。
(f) 为节约加载速度,可增加缓存机制。
(2) 性能需求
(a) 界面简约又不失美观。
(b) 任何操作不造成客户端界面交互无响应。
(c) 在数据交互的过程中需保证数据的安全性。
(d) 及时对服务端响应异常的情况进行处理。
对于该平台软件所需要的一系列相关资料均可以通过互联网和文献材料进行获取采集,由于开源软件的盛行,对于软件所依赖的各种库均可从GitHub网站获取。而国内云计算的发展,无论操作系统还是硬件设备均可从国内云计算公司获取,无需特殊的工具,且开发成本较低,软件程序简单易实现,从经济的角度来看,对于该平台软件的开发经济方面可行。
由于开源软件的盛行,目前主流的分布式文件系统均具备备份与数据迁移功能,同时也能保证数据的安全性。而针对高并发问题,目前Nginx服务器可通过反向代理实现负载均衡,解决了高并发的问题[12]。而随着非关系型数据库的发展,针对高频访问的数据可直接存入非关系型数据库中,这样便显著提高了访问效率。对于客户端与服务端数据如何进行交互,这已经在互联网正式普及之时便已解决,而作为主流可靠的传输方式,HTTP协议已经成为TCP传输的典范。因此,从技术角度来看,通过HTTP协议将客户端数据传输服务端,服务端通过Nginx服务器进行反向代理,监听网络,通过CGI程序将接收到的数据加以处理,数据存储于FastDFS中,信息存储于MySQL,同时Redis加以辅助[13],这种设计方式是可行的。
运行性是对组织结构最直观的影响,它决定着用户的体验感。主要体现在客户端美观与流畅上。而该平台的设计界面通过Qt设计,简约而不失美观,操作十分简单,功能齐全,由于Qt各种动态库获取方便,因此只需要有相应的动态库文件,在Windows操作系统上安装便可运行。
软件的设计开发分为客户端与服务端两部分,客户端主要所采用的工具是Qt 5.4.1,开发出的应用程序客户端在Windows操作系统的计算机上运行,分为用户界面与文件界面两部分,以UI界面窗口的方式呈现给用户,接收鼠标和键盘的输入,以显示器作为载体输出,符合新时代计算机操作系统用户的使用习惯,方便用户对图像数据进行存储和管理,包括用户的登录、注册以及图像的上传、下载、共享、预览、转存、秒传、删除等一系列操作,简单而且容易上手。而服务端作为用户数据的载体,必须保证数据的安全性与可靠性,因此服务端的操作均由服务端代码进行处理,不直接授予用户权限。所以此软件在操作上是可行的。
综上所述,该平台软件的开发与设计从经济、技术、运行、操作等各个方面完全可行。
传统的网络存储系统通过集中的存储方式将全部数据存放到服务器上[14],因此服务器的性能成为数据安全性与可靠性的关键。而针对以数据为根本的大数据时代,显然传统的网络存储方式已不能满足时代的要求。因此,衍生出了分布式文件系统。
分布式文件系统可以简单理解为将对象数据离散地分布存储到许多台独立的存储设备上。分布式文件系统存储方式采用弹性扩展的结构,通过多态弹性服务器来分担数据存储的负荷,通过追踪器服务器来定位存储的结点,这种方式不仅提高了系统的执行效率,而且由于其本身易于扩展的特性,显著提高了系统的实用性与可靠性。
FastDFS分布式文件系统是一款轻量级开源项目,由C语言实现,支持Uinx、Linux等操作系统,为互联网应用开发定制。可用于管理不局限于图像的各种数据,包括对象存储、数据同步、上传、下载、删除等一系列操作[15],解决了大空间存储和负载均衡等问题,追求高性能和弹性扩展。
FastDFS分布式文件系统分为三部分:追踪器Tracker,存储结点Storage,客户端Client。客户端Client和存储结点Storage请求连接追踪器Tracker,Storage主动向Tracker报告空闲存储空间、数据同步状况、上传下载次数等信息。每次启动时存储结点会启动单线程实现对追踪器的连接与汇报信息。FastDFS可划分多个组,每组所包含的存储结点由追踪器获取。整体架构如下图:
图3-1 FastDFS分布式文件系统结构
FastDFS集群可分为追踪器集群与存储结点集群,追踪器集群之间是平等的关系,不存在单点故障的情况,Client访问追踪器通过轮回的方式请求。存储结点集群通过分组的方式集群,存储容量为每个存储结点容量之和[16]。
FastDFS集群的扩容分为横向扩容与纵向扩容,横向扩容可通过添加组的方式进行空间扩充,而纵向扩容用来实现备份,一组内的最大容量为当前组内存储结点中空间最小的服务器容量。集群整体结构如下:
图3-2 FastDFS分布式文件系统集群
Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件代理服务器,采用C进行编写。不仅可以作为HTTP服务器相应Web请求,同时还可以作为反向代理服务器。其特点是占有内存少,并发能力强。Nginx 既可以直接支持Rails和PHP程序,也可以支持作为HTTP代理服务对外服务[17]。
图3-3 反向代理服务器原理
Nginx三个核心功能是静态服务器、反向代理和负载均衡[18]。这三个功能通过配置文件来实现,所有配置选项代码大致分为以下几部分:
main # 全局配置
events { # Nginx工作模式配置
}
HTTP { # HTTP设置
….
server { # 服务器主机配置
….
location { # 路由配置
….
}
location path {
….
}
location otherpath {
….
}
}
server {
….
location {
….
}
}
upstream name { # 负载均衡配置
….
}
}
如上述配置文件所示,主要由6个部分组成:
(a) main:用于全局信息的配置。
(b) events:用于Nginx工作模式的配置。
(c) HTTP:用于进行HTTP协议信息的一些配置。
(d) server:用于进行服务器访问信息的配置。
(e) location:用于进行访问路由的配置。
(f) upstream:用于进行负载均衡的配置。
upstream模块主要负责负载均衡的配置[19]。简单的配置方式如下:
upstream name {
ip_hash;
server 192.168.1.100:8000;
server 192.168.1.100:8001 down;
server 192.168.1.100:8002 max_fails=3;
server 192.168.1.100:8003 fail_timeout=20s;
server 192.168.1.100:8004 max_fails=3 fail_timeout=20s;
}
核心配置信息如下:
(a) ip_hash:指定请求调度算法。
(b) server host:port:分发服务器的列表配置。
(c) down:表示该主机暂停服务。
(d) max_fails:表示失败最大次数,超过失败最大次数暂停服务。
(e) fail_timeout:表示请求失败,指定的时间后重新请求。
Qt作为一款开源的跨平台图形界面开发框架,既可以对GUI进行设计,同时由于其是基于C++的面向对象的框架,因此同样可以作为C++编译软件编写程序。Qt类库中的类大致分为两种类型:一种是独立的不从任何类集成的类;另一种直接继承自Qt类。
独立的类通常在Qt类库中用来实现独立的功能。继承自Qt的类主要分为QEvent类和QObject类。QEvent类是所有QT事件响应类的基类,QObject类是所有应用组件的基类[20]。
QWidget类是组件容器,所有能结合在一起的组件均继承自从该类。而QWidget类则继承自QObject基类[21]。
信号和槽是Qt引进的一种处理机制,信号可以被理解为一个对象发出的事件请求,槽是处理信号的函数。
信号和槽能完成回调函数的所有功能,并且信号和槽机制是类型安全的,而且还能完成其他许多复杂的功能。
信号和槽可以是单一或者多对多的关系。一个信号可以被连接到多个槽,一个槽也可以响应多个信号,此外,信号之间也可以被连接[22]。
该项目的设计采用C/S结构,即客户端/服务端模式。首先,客户端响应用户的请求,将用户的请求封装成Json格式,然后通过HTTP协议发送给服务端。对于密码等敏感信息可通过Md5加密后封装成Json格式发送[23]。在整个数据交互的过程中,客户端始终保证用户操作的合理性。
服务端通过指定端口接收到数据后,首先由Nginx服务器进行反向代理,将数据转发给Nginx配置文件中记录的Web服务器IP。Web服务器接收到数据后根据当前数据HTTP请求头的类型将不同类型数据,通过配置文件中对应的端口转发给服务器相应的CGI程序进行处理,端口对应的CGI程序首先对数据进行拆解,获取数据具体的请求,然后进行处理,并将处理结果转发给客户端。
对于数据对象本身,CGI程序通过分布式文件系统追踪器对存储结点的数据进行操作,对于信息类数据,CGI程序则将对持久性数据库MySQL进行一系列操作,同时缓存数据库Redis加以辅助提高效率。
整体结构如下:
图4-1 总体设计框架
该平台总体包括客户端与服务端两部分。二者之间数据以Json格式通过HTTP协议进行交互。同时对于特殊数据例如密码需进行Md5加密,然后封装Json格式传输。
客户端主要分为界面设计与功能模块两部分。界面设计分为用户界面和文件界面,其中用户界面包括登录界面、注册界面、设置界面;文件界面包括我的文件、共享文件、下载榜、传输列表。功能模块包括基础功能模块与特殊功能模块,其中基础功能统一封装在一个基础功能类中,用于实现包括数据封装与解析、HTTP传输、读取配置文件、加密等功能;特殊功能模块作为工具类,包括实现上传下载任务与进度、图像预览等功能。
服务端主要包括服务端框架搭建与功能模块两部分。服务端框架包括Nginx与负载均衡、MySQL数据库与Redis缓存数据库、分布式文件系统的搭建。功能模块用于实现包括用户注册与登录、文件上传与秒传、文件列表查询、文件共享与转存、文件删除、文件下载等功能[24]。
具体整体设计方案如下图:
图4-2 总体方案结构
由于客户端是用户直接进行数据交互的载体,因此客户端在达到美观简洁的同时需要有平台完整的功能。根据平台的需求,客户端采用Qt编译器进行客户端代码的编写与界面的设计。作为图像文件管理平台,需要为用户提供文件界面供用户进行管理操作;同时为方便用户信息的验证,客户端还需用户界面获取用户的信息。因此客户端包括用户界面与文件界面。
用户界面通过一个主QWidget,包含三个子QWidget,分别作为登录、注册、设置三个子界面,通过不同的按钮切换不同的子界面。登录注册用于用户的身份校验,设置用于设置代理服务器服务器的端口号和IP用于数据交互。
通过登录界面信息校验成功后会关闭用户界面,同时打开文件界面。文件界面包括头部和信息部两部分组成,头部包含多个文件功能切换按钮,信息部界面主要用于对文件信息的展示,因此在设计上,头部作为公共部分只需一个QWidget派生类,而信息部分展示界面通过不同的QWidget派生类与头部拼接成不同的文件界面。
对于服务端,需要保障数据的高并发与流畅性,因此通过一个Nginx服务器配置使得客户端直接连接的服务器具备反向代理与负载均衡的功能。由于Nginx服务器不能直接处理动态数据,因此Web服务器需要借助CGI程序来处理动态响应。处理不同事件的CGI程序绑定Web服务器不同的端口,通过HTTP协议请求头来确定对应端口,供CGI程序处理。对于数据本身,CGI程序通过分布式文件系统提供的API对分布式文件系统进行操作。同时对于信息类数据存于MySQL,将访问量大的数据插入非关系型数据库,以便下次查找直接从非关系型数据库中提取数据。
对于分布式文件系统采用FastDFS,需要配置追踪器与存储节点,使得服务器构建成一个集群。同时对于每一台存储节点服务器,通过Storage与Nginx绑定,使得存储于存储节点服务器的数据可通过Url的方式直接访问,而不需重新下载。这样只需将文件对应的Url存储于数据库中,下载或者访问时直接通过访问数据库中的Url即可,不需再从存储节点中下载后发送给客户端,显著地提高了下载与访问效率。
对于数据库采用MySQL进行数据库设计以及增删改查,根据需求需建五张表,用户表用于用户登录的确认,文件表作为文件信息的保存,共享文件表用于共享文件信息的获取,用户文件表用于用户文件信息的获取,用户文件数量表用于辅助用户访问文件界面时文件的展示,同时为扩展功能提供预留空位。
缓存数据库通过Redis对访问频繁的数据进行存储。每次获取信息先查询Redis,如果没有需要的信息则访问MySQL,同时将查询数据存入Redis,提高数据获取效率。
对于用户模块可按功能具体划分为注册模块与登录模块。二者客户端为通过继承用户界面而派生的两个子界面。服务端主要对数据库进行操作。
注册的客户端逻辑为:用户通过进入注册界面的填写注册信息,客户端代码获取数据后进行逻辑判断,如果用户名或昵称不符合格式要求,则向客户端反馈用户名或昵称格式不符合要求的消息;如果密码不符合格式要求或两次输入密码不一致,则向客户端反馈密码相关操作失败的消息;如果电话或者邮箱不符合规范,则向客户端反馈电话或者邮箱格式不规范的消息。如果用户填写的注册信息均通过客户端校验格式的代码验证,则首先用Md5对密码加密,然后封装成Json格式以HTTP协议发送给服务端。如果服务端长时间未响应则反馈注册失败信息。Web服务端通过请求头为reg交给相应端口的CGI程序。服务端CGI程序的逻辑为:首先将Json数据提取出来,然后查询数据库用户表与通过Json解析的用户名进行比对,如果用户表存在该用户名,则反馈用户名重复的状态码,否则将该注册用户信息插入用户表中,反馈注册成功的状态码。客户端接收到状态码后,通过比对,如果不为成功状态码则根据状态码类型向用户反馈注册失败的原因,并提示用户重新注册;否则客户端会提示用户注册成功,同时会跳转至用户登录的界面,待用户进行登录操作。
注册流程如下:
图4-3 用户注册流程图
登录的客户端逻辑为用户通过登录界面填写用户名与密码,如果选择记住密码则登录成功后会把用户以及用户密码的Md5值保存到缓存配置文件中[25]。用户通过登录界面填写登录信息,点击登录按钮,客户端会通过信号槽将登录信息向服务端发送,Web服务端根据请求头为login交给相应端口的CGI程序。服务端CGI程序的逻辑为首先将Json数据提取出来,然后查询数据库用户表与解析后的用户名与密码进行比对,如果存在该用户名,并且密码Md5值跟数据库中密码存储信息一致则反馈注册成功状态码,否则反馈根据情况反馈登录失败状态码。客户端接收到状态码后,如果不为登录成功状态码则提示用户登录失败,否则客户端跳转至我的文件界面。
登录流程如下:
图4-4 用户登录流程图
当用户进入文件界面后,客户端需要保存当前用户的所有文件信息,因此会向服务端发起当前用户的文件查询请求。当服务端通过请求头myfile将用户信息交给相应端口的CGI程序。CGI程序逻辑为通过以用户名查询数据库user_file_list表将所有文件信息传给客户端,客户端获取到所有信息后,通过QWigitList将文件信息按图标的形式排列到界面,同时在排列过程中对于文件为图像类型的文件进行判断,如果该图像文件在缓存中有缩略图,则显示缩略图,否则显示默认图片。
图4-5 文件列表查询流程图
当用户进行上传操作时,首先是采取秒传的方式,将待上传文件的信息通过Md5加密,然后封装好数据后通过HTTP传输,服务端通过Md5请求头交给负责秒传的CGI程序。CGI程序通过文件信息表中Md5值的比较,判断当前文件信息是否存在于数据库中,如果存在则将该文件信息与该用户通过user_file_list表进行关联,否则返回数据库无该文件信息的状态码。客户端接受到状态码后进行判断,成功则不需再进行上传操作,即实现了秒传,否则需要进一步进行上传操作。
图4-6 数据秒传流程图
上传过程需要客户端维护一个上传队列。客户端选择文件路径后,每次对于确定无法秒传需要上传的文件信息假如到上传队列中。上传时每次从队列中提取队首元素进行请求,上传完成时将该任务从队列中移除。该过程客户端会时时跟进上传进度,并且将进度按进度条的形式更新在数据传输列表界面。
开始上传时,客户端获取到该文件后将文件以二进制流的形式进行HTTP传输。服务端通过请求头upload将用户信息与数据交给相应端口的CGI程序。CGI程序逻辑为首先通过数据库file_info表判断该文件是否存在,若存在则判断该用户是否拥有,是则反馈已拥有状态码,负责对user_file_list与user_file_count表进行修改操作。否则会通过fork创建子进程,子进程将数据上传到分布式文件系统,通过fdfs_upload_file的API将提取出的二进制流文件上传到分布式文件系统,同时获取到分布式文件系统反馈的文件id,让id传给父进程。父进程用于处理数据信息,将文件生成后的信息插入到file_info表中,同时将用户与文件但关联信息插入到user_file_list中,同时对user_file_count表进行更新。文件信息处理好后,给用户反馈成功状态码。在父子进程的过程中任意一步出现问题均反馈失败状态码。客户端接收到状态码后,通过状态码比对,如果是成功状态码则向用户反馈上传成功的信息,否则给用户反馈上传失败的信息。
图4-7 数据上传流程图
当用户请求删除操作时,客户端首先需要进行判断用户请求的文件拥有者是否为该用户,如果不是则提示文件不属于该用户的错误信息;否则向服务端发起请求。服务端接收到请求后,通过请求头dealfile或者dealsharefile将用户信息与数据交给相应端口用于文件操作的CGI程序,CGI程序进行处理。CGI程序的逻辑为首先进行数据库查询,首先对user_file_count表进行减一操作,对user_file_list中该用户与该文件的关联进行删除。最后需要通过file_info表的数量属性进行判断,如果大于1,则将该属性减1,否则将该信息从数据库file_info表中删除。同时子进程需通过分布式文件系统提供的fdfs_delete_file相关API对通过该文件Id删除分布式文件系统中该文件的数据。一切成功后向客户端反馈状态码。客户端收到反馈后刷新界面。
图4-8 文件删除流程图
当用户切换到共享文件界面时,会向服务端发起共享文件列表获取的请求,通过share_file_list表进行共享文件信息的查询,然后将查询到的列表信息发送给客户端。客户端接收到数据后对界面按照图标显示的方式进行刷新。对下载榜界面,服务端会将文件按下载量进行排序,然后发给客户端,客户端通过信息列表的形式进行显示。
当用户发起共享或者取消共享操作后,客户端逻辑首先进行判断用户发起的请求文件是否属于该用户,不是的话反馈错误信息;是的话需要根据该文件信息判断该文件分享状态,如果已是该状态则不需操作,否则会向服务端发起请求。当服务端接收到请求后,通过请求头dealfile或者dealsharefile将用户信息与数据交给相应端口用于文件操作的CGI程序,CGI程序的逻辑为首先提取数据,对数据库share_file_list表进行插入,然后对file_info表等文件相关的表进行更新。最后反馈状态码。
图4-9 文件共享流程图
下载模块首先需保证服务端存储结点Storage与Web处理服务器Nginx相关联,关联后对于存储结点上的数据可直接通过Url进行下载。客户端维护一个下载队列。而下载的流程主要是用户发起下载请求后,客户端会向服务器请求该文件Url信息,收到文件Url后,客户端将下载信息插入下载队列。客户端每次执行队首任务,根据下载信息向相应Url进行Get请求,请求成功后便开始数据下载,同时对于下载进度通过进度条时时更新在数据传输列表界面上。当下载完成时,将该任务从下载队列中移除,然后进行下一个文件下载。
图4-10 文件下载流程图
由于用户进入文件界面后,客户端代码会将当前缓存中的文件数据保存起来,因此当用户点击预览按钮时,首先客户端会判断该文件信息的类型,如果为图像文件方可进行预览操作,否则反馈失败信息。客户端根据当前保存的文件信息进行Url获取,同时通过HTTP协议对该Url进行请求,获取到响应后客户端会通过QLable进行图像的绘制,为用户呈现该图像的完整状态。同时客户端会将该图像文件缓存起来,以便下次再次访问该图像文件时直接对缓存中的数据进行绘制,而不需在向服务器进行请求。
图4-11 图像预览流程图
本项目的流程并不是很复杂,但是由于涉及到的东西较多,因此整个项目流程的清晰度决定了项目实现的难易。
客户端流程分为用户界面流程与文件界面流程两部分。用户通过可执行文件首先加载的是用户界面,通过用户界面的设置栏设置反向代理服务器的IP与端口号,客户端程序保存IP与端口号,之后将所有相关数据发送对于发现代理服务器IP。设置完成后,用户可选择性通过注册栏发送注册信息给服务端,收到服务端注册成功的反馈后跳转至登录界面。用户通过登录界面填写用户名及密码,点击登录后,获取服务端的反馈信息,校验成功则跳转文件界面,否则提示登录失败。
进入文件界面后,默认在我的文件界面,客户端会向服务端发送当前用户信息以及获取当前用户拥有文件信息,得到服务端反馈的信息后,客户端代码将其文件信息绘制到UI界面上,同时如果本地缓存中有相应文件的缓存缩略图,则将默认显示图像替换为缩略图图像,实现预览效果。
文件界面默认主界面有上传按钮,点击后可选择指定文件夹下的一个或多个图像文件进行上传操作,然后会切换到文件界面的传输列表界面。文件主界面空白域右键点击事件包括文件重新排序与上传等操作,其中上传操作与点击上传按钮事件一样。文件默认展示文件类型图像,如果该文件为图像文件且缓存中有该文件的缩略图,则以缩略图的方式显示。文件右键点击事件包括下载、共享、预览、删除、属性等点击事件,对应下载、共享、预览、删除、属性操作。
文件界面包含主界面、共享文件界面、下载榜界面、传输列表界面四部分,在UI头部有相应切换事件响应的按钮。
共享文件界面与下载榜界面的流程与主界面的流程相似,唯一不同的是向服务端请求的是共享文件信息而不是用户文件信息。共享文件界面与下载榜界面的不同在于排列方式不同以及文件信息显示方式不同,共享文件界面的排列方式为按共享时间顺序排列,同时以图标的形势显示文件;而下载榜是以文件下载量的方式进行排序,以详细信息的方式进行显示。
传输列表包含上传列表、下载列表、传输记录三部分,对应上传进度、下载进度、上传下载记录三部分的展示。
主界面同时包含切换用户按钮,点击后跳转登录界面。
图4-12 客户端总体流程图
服务端整体的流程包括数据转发、数据提取、分布式文件系统操作、数据库操作四部分。
首先数据转发部分主要流程是反向代理服务器接收到数据后根据并发权重将数据转发给Web服务器,Web服务器通过配置文件与HTTP请求头信息进行类型解析,根据不同的类型将数据发送给不同的端口所绑定的CGI程序。
数据提取主要为服务端公共代码的任务,将Json数据中的信息提取出来,然后供CGI程序进行响应一系列执行操作。
分布式文件系统通过FastDFS提供的API对数据对象进行上传、下载、删除操作。在此之前需要服务器先搭建好分布式文件系统的集群。由于FastDFS为C语言所写,因此安装后的API直接可以用到服务端C++程序中。
数据库操作主要分为两部分,一部分是通过MySQL进行数据的增删改查,另一部分是对访问量较多的数据储存于Redis[26]。所有程序在对数据库进行操作时,首先会从缓存数据库Redis中查询,如果缓存数据库中已有该数据,则直接获取,否则会从MySQL中进行查询,然后在处理数据的过程中将数据同时插入缓存数据库中。
服务端从功能部分大致分为用户登录与注册、文件上传或删除、文件共享与转存、文件下载与预览几部分。这些功能模块均首先通过数据转发后进行数据提取,然后进行各类型的一系列操作。
用户登录与注册部分主要为数据库操作;文件上传或删除包括分布式文件系统操作与数据库操作两部分;文件的共享与转存操作并未涉及到分布式文件系统操作,主要为数据库操作。
文件下载与预览部分有两种实现方式,第一种也是常规的方式包括分布式文件操作与数据库操作,但这样每次会造成效率的低下,因此通过第三方软件将存储结点Storage与Nginx连接,使得存储结点的数据可直接以Url的方式进行获取,这样只需要进行数据库操作即可,即将Url保存至数据库中,而数据的获取直接通过Url便可下载,而不需经过分布式文件系统追踪器等一系列过程。
图4-13 服务端总体流程图
Nginx负载均衡是在反向代理的基础上完成的,首先用户通过浏览器或者客户端连接到Nginx反向代理服务器,Nginx通过配置文件中的server模块找到server_name映射的域名,通过proxy_pass+Url找到upstream模块,然后访问server相应的地址。upstream模块中可设置多个IP,同时赋予权重,每次用户访问服务器时,Nginx都会按权重将用户请求分配到upstream模块中对应的IP,这样便实现了负载均衡。
负载均衡通过配置Nginx.conf实现[27]。具体负载均衡部分配置样例如下:
upstream test.com{
server 39.96.209.253:80; #服务器1
server 172.17.49.222:80; #服务器2
}
server{
listen 80;
server_name localhost;
Location /{
proxy_pass HTTP://test.com;
}
}
数据库主要用于存储用户与文件的信息,数据信息存于MySQL中。数据库表主要分为五张表,具体表结构如下图所示:
图5-1 数据库表结构
user表用于储存用户相关信息。当用户注册时将信息插入该表中;当用户登录时通过查询该表进行用户身份校验,校验成功方能进入文件界面对图像进行管理。user表中包含name、nickname、password、phone、createtime、email六个栏位,其中name作为用户名,password作为密码,用于登录时校验,其余作为用户信息储存栏位。
表5-1 user表
序号 | 列名 | 数据类型 | 长度 | 主键 | 允许空 | 备注 |
---|---|---|---|---|---|---|
1 | name | varchar | 20 | 是 | 否 | 用户id |
2 | nickname | varchar | 20 | 是 | 用户昵称 | |
3 | password | varchar | 65 | 是 | 用户密码 | |
4 | phone | varchar | 12 | 是 | 电话 | |
5 | createtime | datetime | 0 | 是 | 注册时间 | |
6 | varchar | 30 | 是 | 邮箱 |
file_info表用于储存文件信息。该表包括md5、file_id、url、size、type、count六个栏位。md5作为文件的唯一标识,也可认为是文件的唯一id标识;file_id用于储存文件在分布式文件系统中对应的id即文件名,可通过该id从分布式文件系统中下载文件;url用于储存该文件的获取网址;size用于记录文件的大小;type代表该文件类型;count用于统计有多少用户拥有该文件,主要用于文件删除。
表5-2 file_info表
序号 | 列名 | 数据类型 | 长度 | 主键 | 允许空 | 备注 |
---|---|---|---|---|---|---|
1 | md5 | varchar | 65 | 是 | 否 | 文件id |
2 | file_id | varchar | 100 | 是 | 对象存储id | |
3 | url | varchar | 100 | 是 | 文件url | |
4 | size | bigint | 20 | 是 | 文件大小 | |
5 | type | varchar | 10 | 是 | 文件类型 | |
6 | count | int | 11 | 是 | 拥有者数量 |
user_file_list作为用户与文件的关系桥梁而存在。表共包含user、md5、createtime、filename、shared_status、pv六栏。user代表用户id;md5代表文件id;createtime代表用户拥有该文件的时间;filename代表用户拥有该文件时的文件名;share_status用于表示该文件的分享状态;pv表示该用户对该文件的下载次数。
表5-3 user_file_list表
序号 | 列名 | 数据类型 | 长度 | 主键 | 允许空 | 备注 |
---|---|---|---|---|---|---|
1 | user | varchar | 20 | 否 | 用户id | |
2 | md5 | varchar | 65 | 否 | 文件id | |
3 | createtime | datetime | 0 | 是 | 关联时间 | |
4 | filename | varchar | 100 | 是 | 文件名称 | |
5 | shared_status | int | 11 | 是 | 分享状态 | |
6 | pv | int | 11 | 是 | 下载量 |
share_file_list表记录共享文件的信息。包括user、md5、createtime、filename、pv五个栏位。user代表该文件的分享者;md5代表该文件id;createtime代表分享时间;filename代表该文件被分享时的文件名;pv代表该文件全部的下载次数。
表5-4 share_file_list表
序号 | 列名 | 数据类型 | 长度 | 主键 | 允许空 | 备注 |
---|---|---|---|---|---|---|
1 | user | varchar | 65 | 否 | 分享用户id | |
2 | md5 | varchar | 65 | 否 | 分享文件id | |
3 | createtime | datetime | 0 | 是 | 分享时间 | |
4 | filename | varchar | 100 | 是 | 文件名称 | |
5 | pv | int | 11 | 是 | 下载量 |
user_file_count表主要记录用户当前拥有多少个文件。其主要用于文件列表获取前访问,如果文件列表数量庞大,需先访问用户文件数目,再分批访问。该表共user、count两个栏位。user代表用户id;count代表该用户拥有文件数量。
表5-5 user_file_count表
序号 | 列名 | 数据类型 | 长度 | 主键 | 允许空 | 备注 |
---|---|---|---|---|---|---|
1 | user | varchar | 20 | 是 | 否 | 用户id |
2 | count | int | 11 | 是 | 用户文件数 |
数据对象本身存储与分布式文件系统中。在服务器上安装好FastDFS分布式文件系统后,需要修改配置文件。由于FastDFS分布式文件系统包括追踪器、存储节点、客户端三部分,因此需要修改tracker.conf、storage.conf、client.conf三个配置文件。
先配置Tracker,再添加一个Storage,每添加添加一个Storage,实际上是Storage连接Tracker,Tracker必须存在,否则Storage无法加进来,Client主要用于测试上传、下载文件。
假如有3台以上的服务器或者虚拟机,一台用来作为Tracker、一台用来作为Client、其他多台用来作为Storage,不过前提需要保证这些服务器之间的网络均可以互相PING通。如果并没有足够多的设备,一台服务器可以同时作为Tracker、Storage、Client使用。
首先是tracker.conf的配置,追踪器服务器需要修改,其他服务器可以不用配置。bind_addr需要修改为追踪器服务器所在的IP,port修改为绑定的端口, base_path为日志目录,配置样例如下:
bind_addr=39.96.209.253
port=22122
base_path=/home/fastdfs/tracker
作为存储节点的服务器需要修改storage.conf,主要修改以下部分。group_name表示存储节点所属的组;bind_addr表示存储节点绑定的IP;port为存储节点绑定的端口;base_path表示存储日志目录;store_path_count配置存储目录的个数;store_path0配置具体的存储目录;tracker_server用于配置连接Tracker的时候使用的IP和端口;配置样例如下:
group_name=group1
bind_addr=39.96.209.253
port=23000
base_path=/home/fastdfs/storage
store_path_count=1
store_path0=/home/fastdfs/storage
tracker_server=39.96.209.253:22122
客户端服务器主要通过API请求上传、下载等操作。因此所有用于响应用户请求的HTTP服务器均需对文件client.conf进行配置。主要需要修改两部分:base_path配置日志目录;tracker_server配置连接Tracker服务器时使用的IP和端口。Client配置样例如下:
base_path=/home/fastdfs/client
tracker_server=39.96.209.253:22122
配置文件配置完成后,所有响应请求的HTTP服务器通过Client调用API向Tracker请求上传、下载等操作[28]。对于常用的API,可通过源码的方式获取。文件上传调用fdfs_upload_file,下载调用fdfs_download_file,删除调用fdfs_delete_file[29]。因此对于数据上传、下载或删除请求,我们可以通过加载fdfs_upload_file.c,fdfs_download_file.c,fdfs_delete_file.c获取对应操作的API。
客户端界面分为两部分,用户界面与文件界面。其中用户界面为一个主界面包含注册、登录、设置三个子界面。而文件界面由按钮栏和文件列表栏两部分拼凑而成,文件列表栏作为父类有我的文件界面、共享列表界面、下载榜列表界面和传输列表界面四个派生类,通过按钮栏的点击事件通过信号槽触发回调函数,创建不同的文件列表栏派生类。
用户界面由一个主界面Login类构成,该类由信息栏title_widget与表单栏stackedWidget两部分组成。信息栏主要用于图标、标题等固定信息的展示;表单栏包含三个QWidget子界面login_page、register_page与set_page,分别用于登录、注册与设置。具体结构如下图:
图 5-2 用户界面结构
当触发切换界面按钮事件时,通过信号槽触发回调函数,通过回调函数调用setCurrentWidget(…)方法根据传参子界面参数进行不同界面的切换。用户界面类的声明如下:
class Login : public QDialog{
Q_OBJECT
public:
explicit Login(QWidget *parent = 0);
~Login();
// 设置登陆用户信息的Json包
QByteArray setLoginJson(QString user, QString pwd);
// 设置注册用户信息的Json包
QByteArray setRegisterJson(QString userName, QString nickName, QString firstPwd, QString phone, QString email);
// 得到服务器回复的登陆状态, 状态码返回值为 “000”, 或 “001”,还有登陆section
QStringList getLoginStatus(QByteArray json);
protected:
void paintEvent(QPaintEvent *);
private slots:
void on_register_btn_clicked();
void on_login_btn_clicked();
void on_set_ok_btn_clicked();
private:
// 读取配置信息,设置默认登录状态,默认设置信息
void readCfg();
private:
Ui::Login *ui;
// 处理网络请求类对象
QNetworkAccessManager* m_manager;
// 主窗口指针
MainWindow* m_mainWin;
Common m_cm;
};
其登录主界面效果展示如下:
图5-3 登录界面效果图
用户进入登录界面后,通过点击注册,进入注册界面,点击关闭会返回登录界面。其注册界面效果展示如下:
图5-4 注册界面效果图
文件界面作为文件基础界面,在用户点击登录按钮后启动。文件界面分为导航栏ButtonGroup类与文件列表界面MainWindow类两部分组成。结构如下:
图5-5 文件界面结构
文件列表界面MainWindow中包含ButtonGroup指针,在启动界面时会通过指针先加载导航栏ButtonGroup类。导航栏由我的文件、共享列表、下载榜、传输列表、切换用户5个界面切换按钮以及关闭之类的基础按钮组成,通过信号槽分别对应不同回调函数。其回调函数声明在signals中。ButtonGroup类声明代码声明如下:
class ButtonGroup : public QWidget{
Q_OBJECT
public:
explicit ButtonGroup(QWidget *parent = 0);
~ButtonGroup();
public slots:
// 按钮处理函数
void slotButtonClick(Page cur);
void slotButtonClick(QString text);
void setParent(QWidget *parent);
….
signals:
void sigMyFile(); //我的文件信号
void sigShareList(); //共享文件信号
void sigDownload(); //下载榜信号
void sigTransform(); //传输列表信号
void sigSwitchUser(); //切换用户信号
void closeWindow(); //关闭信号
void minWindow(); //最小化信号
void maxWindow(); //最大化信号
private:
Ui::ButtonGroup *ui;
QPoint m_pos;
QWidget* m_parent;
QSignalMapper* m_mapper;
QToolButton* m_curBtn;
QMap<QString, QToolButton*> m_btns;
QMap<Page, QString> m_pages; //文件列表界面及名称
};
导航栏ButtonGroup 效果界面展示图如下:
图5-6 文件界面导航栏
文件列表MainWindow作为父类共包含我的文件界面MyFileWg类、共享列表界面ShareList类、下载榜界面RankingList类和传输列表界面Transfer类四个派生类,这四个界面均继承自MainWindow类。通过不同的按钮触发信号槽对应的回调函数绘制不同的子界面。
文件列表界面父类MainWindow具体声明代码如下:
class MainWindow : public QMainWindow{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
void showMainWindow(); // 显示主窗口
void managerSignals(); // 处理信号
void loginAgain(); // 重新登陆
signals:
void changeUser(); // 切换用户按钮信号
protected:
void paintEvent(QPaintEvent *event);
private:
Ui::MainWindow *ui; //文件界面指针
Common m_common; //工具类
};
图5-7 我的文件效果图
图5-8 共享列表效果图
图5-9 下载榜效果图
图5-10 传输列表效果图
客户端在进行界面设计的同时为了避免代码冗余,需要对一些比较常用的方法进行抽离,实现高内聚低耦合。因此将这些常用的方法封装成工具类,放在Common文件夹里。该文件夹包含基础类文件和工具类文件。基础类汇集在common.cpp,工具类包括Md5加密算法des.c、用户信息保存logininfoinstance.cpp、下载任务列表downloadtask.cpp、下载进度界面downloadlayout.cpp、上传任务列表uploadtask.cpp、上传进度界面uploadlayout.cpp。
基础类头文件common.h包含宏定义配置,包括配置文件的宏定义与正则表达式的宏定义,同时包括文件类的属性声明。基础类Common包括读写配置文件、读写日志、文件类型判断、Md5加密、HTTP通信、获取状态码等方法的实现。具体声明如下:
class Common : public QObject{
Q_OBJECT
public:
Common(QObject* parent = 0);
~Common();
// 窗口在屏幕中央显示
void moveToCenter(QWidget *tmp);
// 从配置文件中得到相对应的参数
QString getCfgValue(QString title, QString key, QString path = CONFFILE);
// 通过读取文件, 得到文件类型, 存放在typeList
void getFileTypeList();
// 得到文件后缀,参数为文件类型,函数内部判断是否有此类型,如果有,使用此类型,没有,使用other.png
QString getFileType(QString type);
// 登录信息,写入配置文件
void writeLoginInfo(QString user, QString pwd, bool isRemeber, QString path = CONFFILE);
// 服务器信息,写入配置文件
void writeWebInfo(QString ip, QString port, QString path=CONFFILE);
// 获取某个文件的Md5码
QString getFileMd5(QString filePath);
// 将某个字符串加密成Md5码
QString getStrMd5(QString str = “”);
// 产生分隔线
QString getBoundary();
// 得到服务器回复的状态码, 返回值为 “000”, 或 “001”
QString getCode(QByteArray json);
// 传输数据记录到本地文件,user:操作用户,name:操作的文件, code: 操作码, path: 文件保存的路径
void writeRecord(QString user, QString name, QString code, QString path = RECORDDIR);
// 得到HTTP通信类对象
static QNetworkAccessManager* getNetManager();
public:
static QStringList m_typeList;
private:
// 文件类型路径
static QString m_typePath;
static QNetworkAccessManager *m_netManager;
};
其配置反向代理服务器IP与端口的设置界面如下:
图5-11 配置设置效果图
工具类LoginInfoInstance为单例模式,当用户信息确认后会将当前用户信息与服务器信息保存到堆中,每次当需要获取用户信息或服务器信息时直接从堆中获取。
工具类中下载任务列表DownloadTask、下载进度界面DownloadLayout、上传任务列表UploadTask、上传进度界面UploadLayout均采用单例设计模式。
Layout作为客户端界面,当客户端访问数据传输列表界面时会加载该单例模式界面。其界面如下:
图5-12 任务进度图
Task作为任务,维护一个队列,当有任务时会将任务插入队列中,每次提取队首任务进行上传、下载操作。当任务完成时从队列中删除该任务。以上传任务为例,其界面效果图如下:
图5-13上传任务界面效果图
由于客户端跟服务端数据之间需要不断交互,但客户端和服务端无论在操作系统还是开发工具上都截然不同,因此数据需要采用统一的格式去存储。而Json作为跨平台的数据交互方式,因此可以将数据封装成Json格式,进行交互。
封装与提取的过程由于客户端与服务端的开发工具不同因此实现方式也不同。首先对于服务端,cJSON是由纯C实现的,跨平台性较好。cJSON是采用链表存储的。
cJSON库在使用时只需两步:将cJSON.c和cJSON.h添加到项目即可;如果在命令行进行链接还需加上-lm表示链接math库[30]。
对于客户端,由于Qt将C++封装成了一套专属于Qt的语言,同时有一套自己的框架,因此对于Json的处理Qt有操作Json数据的中心类QJsonDocument。因此对于解析Json数据只需调用API即可,而对于封装Json数据的方法代码如下:
void Common::writeWebInfo(QString ip, QString port, QString path){
// Web_server信息
QMap<QString, QVariant> web_server;
web_server.insert(“ip”, ip);
web_server.insert(“port”, port);
// type_path信息
QMap<QString, QVariant> type_path;
type_path.insert(“path”, m_typePath);
// login信息
QString user = getCfgValue(“login”, “user”);
QString pwd = getCfgValue(“login”, “pwd”);
QString remember = getCfgValue(“login”, “remember”);
QMap<QString, QVariant> login;
login.insert(“user”, user);
login.insert(“pwd”, pwd);
login.insert(“remember”, remember);
QMap<QString, QVariant> json;
json.insert(“web_server”, web_server);
json.insert(“type_path”, type_path);
json.insert(“login”, login);
QJsonDocument jsonDocument = QJsonDocument::fromVariant(json);
file.write(jsonDocument.toJson());
file.close();
}
对于用户部分,除了客户端界面以外,服务端的实现主要是对于数据库的操作,首先Web服务器Nginx将登录请求交由10000端口CGI程序进行处理,将注册请求交由10001端口CGI程序进行处理,需要配置如下:
location /login{
fastcgi_pass 127.0.0.1:10000;
include fastcgi.conf;
}
location /reg{
fastcgi_pass 127.0.0.1:10001;
include fastcgi.conf;
}
Shell命令需要将端口与CGI程序绑定,脚本如下:
spawn-fcgi -a 127.0.0.1 -p 10000 -f ./bin_cgi/login
spawn-fcgi -a 127.0.0.1 -p 10001 -f ./bin_cgi/register
通过配置文件将数据传递给对应端口的CGI程序,登录模块的核心部分是数据库信息的比对与结果的封装,数据封装通过数据处理模块完成,而核心的SQL操作语句如下:
sprintf(sql_cmd, “select password from user where name=\”%s\””, user);
注册部分对于数据库操作的核心代码如下:
sprintf(sql_cmd, “insert into user (name, nickname, password, phone, createtime, email) values (‘%s’, ‘%s’, ‘%s’, ‘%s’, ‘%s’, ‘%s’)”, user, nick_name, pwd, tel, time_str ,email);
文件上传分为秒传与上传,秒传是对于服务端已有该文件的情况下无需再对文件进行分布式文件系统存储,而直接更改数据库,上传则需要将文件上传到分布式文件系统,
对于客户端的上传请求,首先客户端将上传请求插入到上传队列里,对于完成的请求从队列中删除。
当第0号任务开始进行上传工作时,客户端将文件信息封装后向服务器发起秒传请求,服务端接收到请求后对请求头进行判断,如果是Md5,则将数据信息传给10003端口绑定的CGI程序。CGI程序对数据库进行查询,如果有该文件信息,则直接更改数据库,通过file_info表将文件信息和该用户信息相关联,然后反馈成功状态码。否则反馈失败状态码。Web服务器Nginx需要配置如下:
location /Md5{
fastcgi_pass 127.0.0.1:10003;
include fastcgi.conf;
}
Shell命令需要将端口与CGI程序绑定,脚本如下:
spawn-fcgi -a 127.0.0.1 -p 10003 -f ./bin_cgi/Md5
客户端接收到服务端的反馈信息后,如果状态码提示服务器没有该文件,则意味着无法秒传,需继续进行数据上传操作。客户端向服务端发送请求:
QNetworkReply * reply = m_manager->post( request, data );
if(reply == NULL){
cout << “reply == NULL”;
return;
}
服务端接收到Post请求后对请求头进行判断是否为upload,将数据信息传给10002端口绑定的CGI程序。CGI程序对数据库进行添加文件信息操作,同时通过fdfs_upload_file提供的API将数据信息上传到服务器分布式文件系统中。Web服务器Nginx需要配置如下:
location /upload{
fastcgi_pass 127.0.0.1:10002;
include fastcgi.conf;
}
Shell命令需要将端口与CGI程序绑定,脚本如下:
spawn-fcgi -a 127.0.0.1 -p 10002 -f ./bin_cgi/upload
客户端的数据传输列表界面时时跟进当前的上传进度,当有可用数据更新时,会刷新界面。跟进的信号槽代码如下:
connect(reply, &QNetworkReply::uploadProgress, ={
if(totalBytes != 0) {
dp->setProgress(bytesRead/1024, totalBytes/1024); //设置进度条
}
});
对于客户端的请求,首先Web服务器Nginx将文件信息请求交由10004端口CGI程序进行处理,将共享列表请求交由10006端口CGI程序进行处理,需要配置如下:
location /myfiles{
fastcgi_pass 127.0.0.1:10004;
include fastcgi.conf;
}
location /sharefiles{
fastcgi_pass 127.0.0.1:10006;
include fastcgi.conf;
}
Shell命令需要将端口与CGI程序绑定,脚本如下:
spawn-fcgi -a 127.0.0.1 -p 10004 -f ./bin_cgi/myfiles
spawn-fcgi -a 127.0.0.1 -p 10006 -f ./bin_cgi/sharefiles
对于CGI程序,文件信息与共享文件分别交给myfiles.c与sharefiles.c处理。以myfiles.c为例,首先会对数据提取的信息状态进行比对,判断具体请求,比如下载榜需将请求数据排序。对于文件信息列表请求的数据库核心代码如下:
sprintf(sql_cmd, “select user_file_list.*, file_info.Url, file_info.size, file_info.type from file_info, user_file_list where user = ‘%s’ and file_info.Md5 = user_file_list.Md5 limit %d, %d”, user, start, count);
对于客户端的删除、共享、取消共享、转存等请求,首先Web服务器Nginx将文件信息请求交由10004端口CGI程序进行处理,将共享列表请求交由10006端口CGI程序进行处理,需要配置如下:
location //dealfile{
fastcgi_pass 127.0.0.1:10005;
include fastcgi.conf;
}
location //dealsharefile{
fastcgi_pass 127.0.0.1:10007;
include fastcgi.conf;
}
Shell命令需要将端口与CGI程序绑定,脚本如下:
spawn-fcgi -a 127.0.0.1 -p 10005 -f ./bin_cgi/dealfile
spawn-fcgi -a 127.0.0.1 -p 10007 -f ./bin_cgi/sharefiles
对于CGI程序,需要将文件划分为普通文件与共享文件,普通文件可直接操作,而共享文件需要进行判断共享文件拥有者。普通文件与共享文件的处理分别交给dealfile.c与sharefiles.c处理。以dealfile.c为例,首先会对数据请求类型进行判断,请求类型具体分为删除、分享、取消分享、转存等类型,四者均对数据库进行增删改查操作,只有删除操作在确认该文件拥有者为0时会对分布式文件系统中的文件进行删除,具体删除FastDFS中文件的核心代码如下:
int remove_file_from_storage(char *fileid){
int ret = 0;
//读取fdfs client 配置文件的路径
char fdfs_cli_conf_path[256] = {0};
get_cfg_value(CFG_PATH, “dfs_path”, “client”, fdfs_cli_conf_path);
char cmd[1024*2] = {0};
sprintf(cmd, “fdfs_delete_file %s %s”, fdfs_cli_conf_path, fileid);
ret = system(cmd);
return ret;
}
由于服务器端存储结点Storage与Nginx关联,因此存储在存储结点的数据可直接通过Url的方式进行获取,提高了服务端下载的效率。对于客户端,维护一个静态队列,队列中存储需要下载的任务信息。每次移除已经下载完成的任务,将最新下载请求进行数据封装后插入下载队列。当队列第0个任务元素需要下载时,首先通过该文件Url发起HTTP请求,具体Get请求代码如下:
QNetworkReply * reply = m_manager->get( QNetworkRequest(Url) );
if(reply == NULL){
p->dealDownloadTask(); //删除任务
cout << “get err”;
return;
}
获取请求的数据完成时,就会发送信号SIGNAL(finished()),代码如下:
connect(reply, &QNetworkReply::finished, ={
cout << “下载完成”;
reply->deleteLater();
p->dealDownloadTask();//删除下载任务
m_cm.writeRecord(user, filename, “010”); //下载文件成功,记录
dealFilePv(Md5, filename); //下载文件pv字段处理
});
reply在有数据时发出readyRead信号,我们便可保存数据。具体代码如下:
connect(reply, &QNetworkReply::readyRead, ={
if (file != NULL){
file->write(reply->readAll());
}
});
有可用数据更新时,会通过信号槽刷新进度界面,代码如下:
connect(reply, &QNetworkReply::downloadProgress, ={
dp->setProgress(bytesRead, totalBytes);//设置进度
});
由于图像Url信息保存在数据库中,而在文件列表请求的过程中会默认将Url保存到文件信息中,因此对于图像预览,需要根据传进来不同的Url参数进行请求。首先创建QNetworkAccessManager进行HTTP通信,然后建立信号连接槽,通过Get的方式向Url发起请求。流程如下:
void PreviewImg::load_network_img(QString Url){
currentPicture = new QPixmap;
//获取网络图片
QNetworkAccessManager *manager;
manager = new QNetworkAccessManager(this);
connect(manager, SIGNAL(finished(QNetworkReply*)),
this, SLOT(replyFinished(QNetworkReply*)));
manager->get(QNetworkRequest(QUrl( Url )));
}
当请求完成时会调用完成回调函数,绘制预览图像界面,同时如果用户选择缓存图片,则会对缩略图及缓存图片进行本地缓存,之后访问直接先通过缓存加载图像,加快下一次访问时的效率。回调函数如下:
void PreviewImg::replyFinished(QNetworkReply *reply){
if (reply->error() == QNetworkReply::NoError){
currentPicture->loadFromData(reply->readAll());//获取字节流QPixmap对象
QString filename = cachePathFile;
currentFileName = filename;
if(flag){//true预览图片,false加载缩略图
showImgFile(currentPicture);//加载预览界面
}else{
PreviewImg::isTask = false;//缩略图请求任务结束
}
if(isCache){ //是否缓存图片
currentPicture->save(cachePathFile);//保存图片
saveIcoFile(currentPicture);//保存缩略图
}
}
}
图5-14 图像预览列表图
任何项目在开发过程中均或多或少会遇到各种各样的错误,除了比较基础的配置环境问题或语法错误以外,还有很多问题是在程序运行的过程中才能发现的。因此在程序设计的同时,尽量使每个模块独立,减少文件之间因耦合度过高而出现错误的几率,达到高内聚低耦合的效果,这样在每个模块设计完成的同时可以独立测试,方便排查错误,提高修复错误的效率。
在程序调试的程序的过程中,服务端主要通过Linux自带的GDB工具进行断点调试,排查错误语法;客户端主要通过Qt Creator语法错误提示和Debug断点单步调试,迅速定位错误代码并显示错误信息。
软件测试重点在于在真实的软件运行环境下通过与软件需求进行比较,发现软件与需求有所偏差的地方。
此次软件测试的目的在于检查基于云存储的图像管理平台已完成的功能,发现软件在运行过程中出现的错误并及时解决,使软件能正常运行,确保每个功能可以正常使用,同时尽量保证软件的功能基本符合软件需求。
QTestLib为用户提供了一个操作简便的单元测试框架,因此本次测试使用Qt Creator 自带的单元测试框架QTestlib进行测试。
本平台的设计步骤通过断点调试、单元测试、集成测试、系统测试四部分对系统的功能和性能进行检查和完善[31]。
首先是单元测试,单元测试以功能模块或函数作为对象,通常采用白盒测试技术,辅以黑盒技术,集中地对程序所实现的每个功能模块进行测试,竭力覆盖每个函数,检验各个程序模块是否存在问题,与所描述的功能是否相符合。
其次是集成测试,把已通过单元测试完成的模块进行组合,最终组成所预期的平台系统。其作用主要是检验软件体系的结构是否合理,检查接口之间的问题。
最后是系统测试,把已经集成测试的软件纳入实际运行环境中,与硬件、外设等支持软件运行的系统因素组合,进行环境相关的测试。
QTestLib为用户提供了一个操作简便的单元测试框架,使用时可以通过在新建项目栏点击其他项目-Qt单元测试,也可以直接在工程文件里加入语句Qt+=testlib。其基本操作代码如下:
class Untitled2Test : public QObject {
Q_OBJECT
public:
Untitled2Test();
private Q_SLOTS:
void initTestCase();
void cleanupTestCase();
void testCase1();
};
Untitled2Test::Untitled2Test() {
…
}
void Untitled2Test::initTestCase() {
…
}
void Untitled2Test::cleanupTestCase() {
…
}
void Untitled2Test::testCase1() {
QVERIFY2(true, “Failure”);
}
QTEST_APPLESS_MAIN(Untitled2Test)
#include “tst_untitled2test.moc”
其中,首个测试函数执行前调用initTestCase();最后一个测试函数执行后调用cleanupTestCase();每个测试函数执行前调用init();每个测试函数执行后调用cleanup()[32]。
对于GUI相关的操作,Qtestlib同样提供了接口。通过对控件传递信息来检查结果。常用的操作函数例如鼠标的点击事件QTest::mouseClick(),以及键盘的点击事件QTest::keyClick()[33]。
图6-1 GUI单元测试部分结果
功能测试通过对平台已实现的功能进行单元测试与集成测试,查看功能在运行状况下是否与预期的功能效果相符,同时查找在单步调试中未发现而在程序运行中会出现的潜在错误。以下是功能测试数据及结果。
表6-1 功能测试结果
功能模块 | 前置条件 | 测试步骤 | 预期结果 | 实测结果 |
---|---|---|---|---|
设置测试-IP | 进入设置界面 | IP输入不规范 | 提示IP不正确 | 提示IP不正确 |
设置测试-端口 | 进入设置界面 | 端口输入不规范 | 提示端口不正确 | 提示端口不正确 |
设置测试-集成测试 | 进入设置界面 | IP与端口输入均符合规范 | 自动跳转至登录界面 | 自动跳转至登录界面 |
设置测试-集成测试 | 进入注册界面,信息输入正确 | 未设置端口和IP | 提示注册连接失败 | 提示注册连接失败 |
设置测试-集成测试 | 进入登录界面,信息输入正确 | 未设置端口和IP | 提示登录连接失败 | 提示登录连接失败 |
用户注册测试-必填项 | 进入用户注册页面 | 信息栏为空,点击注册 | 提示用户注册信息为空 | 提示用户注册信息为空 |
用户注册测试-边界值 | 进入用户注册页面 | 输入用户名不规范,其他信息规范 | 提示注册失败 | 提示用户名不正确 |
用户注册测试-边界值 | 进入用户注册页面 | 输入昵称不规范,其他信息规范 | 提示注册失败 | 提示昵称不正确 |
用户注册测试-边界值 | 进入用户注册页面 | 输入密码不规范,其他信息规范 | 提示注册失败 | 提示密码不正确 |
用户注册测试-边界值 | 进入用户注册页面 | 输入两次密码不同,其他信息规范 | 提示注册失败 | 提示两次密码不同 |
续表 6-1
功能模块 | 前置条件 | 测试步骤 | 预期结果 | 实测结果 |
---|---|---|---|---|
用户注册测试-边界值 | 进入用户注册页面 | 输入电话不规范,其他信息规范 | 提示注册失败 | 提示电话不正确 |
用户注册测试-边界值 | 进入用户注册页面 | 输入邮箱不规范,其他信息规范 | 提示注册失败 | 提示邮箱格式不正确 |
用户注册测试-集成测试 | 进入用户注册页面,IP与端口可用 | 填写用户名存在,其他信息填写正确 | 提示注册失败 | 提示用户已存在 |
用户注册测试-集成测试 | 进入用户注册页面,IP与端口可用 | 填写注册均信息规范且用户名不存在 | 提示注册成功,跳转登录界面 | 提示注册成功,跳转登录界面 |
用户登录测试-必填项 | 用户已经注册并激活账户 | 用户名为空,密码规范,点击登录 | 提示用户名为空 | 提示用户名为空 |
用户登录测试-必填项 | 用户已经注册并激活账户 | 密码为空,用户名规范,点击登录 | 提示密码为空 | 提示密码为空 |
用户登录测试-边界值 | 用户已经注册并激活账户 | 用户名规范,密码不规范,点击登录 | 登陆失败 | 登陆失败 |
用户登录测试-边界值 | 用户已经注册并激活账户 | 密码规范,用户名不规范,点击登录 | 登陆失败 | 登陆失败 |
用户登录测试-边界值 | 用户已经注册并激活账户 | 密码与用户名均不规范,点击登录 | 登陆失败 | 登陆失败 |
用户登录测试-集成测试 | 用户已经注册账户 | 用户名和密码输入正确,点击登录 | 登录成功,跳转至我的文件界面 | 登录成功,跳转至我的文件界面 |
用户登录测试-缓存测试 | 用户已经注册账户 | 选择记住密码登录,再进登录界面 | 登录栏有上次用户输入的信息 | 登录栏有上次用户输入的信息 |
文件上传测试-文件类型 | 1.用户已登录,并进入文件页面;2.准备1个非图像文件 | 1.选择非图像文件文件;2.确认上传 | 提示上传成功,文件信息保存至数据库中 | 提示上传成功,文件信息保存至数据库中 |
文件上传测试-文件类型 | 1.用户已登录,并进入文件页面;2.准备1个图像文件 | 1.选择图像文件;2.确认上传 | 提示上传成功,文件信息保存至数据库中 | 提示上传成功,文件信息保存至数据库中 |
文件上传测试-性能测试 | 1.用户已登录,并进入文件页面;2.准备10个文件 | 1.选择准备好的文件;2.确认上传 | 10个文件加入到上传队列 | 10个文件加入到上传队列 |
续表 6-1
功能模块 | 前置条件 | 测试步骤 | 预期结果 | 实测结果 |
---|---|---|---|---|
文件上传测试-性能测试 | 用户已登录,并进入文件页面 | 1.选择1KB文件;2.确认上传 | 成功上传 | 成功上传 |
文件上传测试-性能测试 | 用户已登录,并进入文件页面 | 选择准备的100MB文件;确认上传 | 成功上传 | 成功上传 |
文件秒传测试-性能测试 | 1.用户已登录,并进入文件页面;2.准备已上传过的文件 | 1.选择准备好的文件;2.确认上传 | 直接提示上传成功,文件信息保存至数据库中 | 直接提示上传成功,文件信息保存至数据库中 |
文件下载测试 | 1.用户已经登陆;2.已经有上传好的文件 | 1.选择一个自己文件点击下载;2.在本地打开文件 | 文件成功打开,且格式正确 | 文件成功打开,且格式正确 |
文件下载测试-共享文件 | 1.用户已经登陆;2.系统中有共享文件存在 | 在共享文件界面点击一个文件下载,然后打开下载文件 | 文件成功打开,且格式正确 | 文件成功打开,且格式正确 |
文件下载测试-共享文件 | 1.用户已经登陆;3.系统中有共享文件存在 | 在共享文件界面点击一个文件下载,然后再上传该文件 | 文件采用秒传方式上传成功 | 文件采用秒传方式上传成功 |
文件共享测试 | 1.用户已经登陆;2.文件列表存在文件 | 用户选择文件设为共享并进共享页面 | 用户1可以查找到该文件 | 用户1可以查找到该文件 |
文件共享测试 | 1.准备两个用户,且已经登陆;2.文件列表存在文件 | 1.用户1选择一个文件共享;2.用户2转存该文件 | 用户2可以在共享界面转存该文件 | 用户2可以在共享界面转存该文件 |
文件共享测试 | 1.准备两个用户,且已经登陆;2.文件列表存在文件 | 1.用户1选择一个文件共享;2.用户2取消共享该文件 | 用户2不可以在共享界面取消共享该文件 | 用户2不可以在共享界面取消共享该文件 |
文件共享测试 | 1.准备两个用户,且已经登陆;2.文件列表存在文件 | 1.用户1选择文件取消共享;2.用户2前往共享页面 | 用户2不可以找到该文件 | 用户2不可以找到该文件 |
文件共享测试 | 1.用户已经登陆;2.文件列表存在文件 | 用户将转存文件共享 | 用户不可以共享该文件 | 用户不可以共享该文件 |
文件列表测试-我的文件 | 1.用户已经登录;2.该存在文件 | 进入我的文件 | 图标形式显示用户所有文件 | 图标形式显示用户所有文件 |
续表 6-1
功能模块 | 前置条件 | 测试步骤 | 预期结果 | 实测结果 |
---|---|---|---|---|
文件列表测试-共享文件 | 1.用户已经登录;2.存在共享文件 | 进入共享文件 | 图标形式显示所有共享文件 | 图标形式显示所有共享文件 |
文件列表测试-下载榜 | 1.用户已经登录;2.存在共享文件 | 进入下载榜 | 以下载量排序共享文件信息 | 以下载量排序共享文件信息 |
文件删除测试-删除普通文件 | 1.用户已经登录;2.系统中存在文件 | 1.用户选择一个文件删除 | 用户文件列表中没有该文件 | 用户文件列表中没有该文件 |
文件删除测试-删除共享文件 | 1.准备两个用户,且已经登录;2.系统中存在文件 | 1.用户1删除自己的共享文件;2.用户2进入共享页面 | 用户2不可以找到该文件 | 用户2不可以找到该文件 |
文件删除测试-删除共享文件 | 1.用户已经登录;2.系统中存在文件 | 用户选择非自己的共享文件点击删除 | 删除失败 | 删除失败 |
文件删除测试-删除共享文件 | 用户1用户2均拥有该共享文件,且用户1为共享者 | 1.用户1删除该文件2.用户2进入共享文件界面查找 | 用户2不可以查找到该文件 | 用户2不可以查找到该文件 |
文件删除测试-删除共享文件 | 用户1用户2均拥有该共享文件,且用户2为共享者 | 1.用户1删除该文件2.用户2进入共享文件界面查找 | 用户2可以查找到该文件 | 用户2可以查找到该文件 |
图像预览测试 | 用户拥有已上传非图像文件 | 选中非图像文件右键点击预览 | 提示该文件非图像,无法预览 | 提示该文件非图像,无法预览 |
图像预览测试 | 用户拥有已上传图像文件并点击预览 | 选中图像文件右键点击预览 | 显示预览效果 | 显示预览效果 |
图像预览测试-缩略图 | 进入文件列表界面,缓存没有预览图 | 无 | 先显示默认图像,一段时间后部分图像逐渐显现缩略图 | 先显示默认图像,一段时间后部分图像逐渐显现缩略图 |
图像预览测试-缩略图 | 进入文件列表界面,缓存有预览图 | 无 | 显示图像缩略图 | 显示图像缩略图 |
图像预览测试-缩略图 | 进入文件列表界面 | 1.预览图像文件2.刷新文件列表 | 预览过的图像文件显示图像缩略图 | 预览过的图像文件显示图像缩略图 |
图像预览测试-缓存 | 进入文件列表界面 | 右键取消缓存,刷新界面 | 未在缓存中的缩略图不会自动加载 | 未在缓存中的缩略图不会自动加载 |
图像预览测试-缓存 | 进入文件列表界面 | 右键点击清理缓存 | 缓存文件清空 | 缓存文件清空 |
用户切换测试 | 进入文件界面 | 点击切换用户 | 切换到登录界面 | 切换到登录界面 |
在性能测试中主要通过SpeedTest性能测试工具监控指定进程,通过上传或下载指定大小的数据来测试上传与下载过程中数据接收的速度。在文件上传或下载的速度测试过程中以采样时间段速度的形式进行平均速度的计算。以下是采样测试结果。
图6-2 虚拟机服务端环境性能测试
图6-3 阿里云服务端环境性能测试
作为大学四年知识汇聚的结晶,基于云存储的图像管理平台的设计与实现最终顺利完成。该平台尽管采用C++设计,但所包含的技术点却很多。
首先服务端的架构就包含分布式文件系统、持久化数据库、非关系型数据库、负载均衡等比较新但应用很广的技术,这些技术原本只能通过书籍或课堂接触,而通过本平台的设计均得到了实践,因此对今后应用的开发有着启迪性的作用;同时,对于目前服务端主流的分布式文件系统、Redis、MySQL、Nginx等应用较广的技术的使用越来越熟练。由于服务端部署在Linux操作系统上,同时用到很多云计算相关技术,为之后无论是应用开发还是大数据开发均提供了宝贵的经验。
客户端通过Qt设计,而Qt作为C++跨平台界面开发的工具,里面支持包括Lambda表达式等一系列C++11特性,同时包含很多通过C++实现的比较新的技术。在设计客户端的同时,不仅巩固了C++基础,同时也为客户端相关的开发积累了丰富的经验。
刚开始设计该平台时,我还没有多少项目开发经验,但由于该应用的功能正好用处很广,不仅可以用来作为文件存储的云盘,同时还可以作为图床供博客使用,因此满怀兴趣的我开始查阅云存储相关的资料。在学习的过程中,不仅掌握了目前时代较前沿的技术,同时也拓展了视野,认识到纸上得来终觉浅,作为开发者不仅需要不断学习,更重要的是进行实践,有了足够的设计经验,对于新的技术学起来会非常快,这样才能跟随时代发展而不被时代所甩掉。
该平台作为较完整的项目,我将会通过GitHub将该平台开源化,供其他开发者使用的同时进行学习与交流,为开源软件贡献一份尽管微不足道但充满诚意的力量。
首先,我要感谢我的指导老师——齐勇老师。此次基于云存储的图像管理平台设计与实现以及毕业论文的撰写与完善均是在齐勇老师的耐心辅导下完成的,从毕业设计开题到毕业论文的完善,齐老师都定期查看,悉心辅导,同时推荐了许多有用的材料,给予我很多有价值的建议。尽管由于一系列不可控因素的存在,导致此次毕业设计齐老师只能通过线上的方式指导,但齐老师那对于学术的严谨态度、对于项目的精益求精品质都使我受益匪浅。正因为齐老师这种认真负责的责任感以及对学生细致入微的关爱,让我一次次在面对压力与困难时从未妥协,也正因为有齐老师的指导我才可以顺顺利利地完成这次毕业设计。
其次,我还要感谢和同一毕业小组的同学们,在完成毕业设计的时光里我们互相勉励,一起解决了毕业设计中的一个个问题,一起进步与成长。纵使日后各奔东西,曾经的这份共情仍在。
在我们即将踏出校门步入社会之际,我的思绪万千,曾几何时懵懵懂懂地踏入大学校门,连最基本的编程都还没学,而现在已经能够独立自主设计一个完整的项目,这背后不仅包含自身的努力与付出,更映射着学校的栽培、老师的关爱、辅导员的呵护以及同学们的互助。因此我向你们致以我最真挚的感激,感谢一路有你们伴我度过这段有欢笑、有快乐、有付出、有收获的大学时光。
[1] 张雨, 蔡鑫, 李爱民, 等. 分布式文件系统与 MPP 数据库的混搭架构在电信大数据平台中的应用[J]. 电信科学, 2013, 29(11): 12-16.
[2] 王波. 基于FastDFS的轻量级分布式文件系统的设计与实现[D]. 东北大学,2013.
[3] 巨头的云计算,谁将是下一个霸主?[J]. 软件, 2018, 39(06): 224-227.
[4] 杨正洪. 智慧城市: 大数据, 物联网和云计算之应用[M]. 清华大学出版社, 2017.
[5] 陈肇雄. 推进工业和信息化高质量发展[J]. 网信军民融合,2019(07):5-7.
[6] 徐为成. 5G时代 云计算发展的五大新趋势[J]. 通信世界,2019(20):46.
[7] Li S, Da Xu L, Zhao S. 5G Internet of Things: A survey[J]. Journal of Industrial Information Integration, 2018, 10: 1-9.
[8] 本刊讯. 工信部印发《云计算发展三年行动计划(2017-2019年)》[J]. 中国公共安全, 2017(05): 20.
[9] Singh J. Study on challenges, opportunities and predictions in cloud computing[J]. International Journal of Modern Education and Computer Science, 2017, 9(3): 17.
[10] Jiang C, Wang Y, Ou D, et al. Energy efficiency comparison of hypervisors[J]. Sustainable Computing: Informatics and Systems, 2019, 22: 311-321.
[11] Mesran M, Abdullah D, Hartama D, et al. Combination Base64 and Hashing Variable Length for Securing Data[C]//Journal of Physics: Conference Series. 2018, 1028: 012056.
[12] 刘佳祎,崔建明,智春. 基于Nginx服务器的动态负载均衡策略研究[J/OL]. 桂林理工大学学报:1-11[2020-06-22].
[13] Hallberg J. Memcached och Redis cachning på lokalt system i en dator[J]. 2019.
[14] 张兰. 基于云计算的电子商务数据缓存处理的研究[J]. 电脑知识与技术,2016,12(23):249-250.
[15] 余庆. 分布式文件系统 FastDFS 架构剖析[J]. 程序员, 2010 (11): 63-65.
[16] 阮光耀. 基于负载均衡的FastDFS新存储节点的同步任务分配策略研究[D]. 武汉纺织大学,2019.
[17] 赵晔. 基于Nginx的Web后端服务器集群负载均衡技术的研究与改进[D]. 昆明理工大学,2017.
[18] Chen X, Song Y, Ye H, et al. Research and Implementation of Digital Ancient Book Library Based on Solr/Lucene[J]. 2018.
[19] 许红军. 灵活配置,强化Nginx安全管理[J]. 网络安全和信息化,2018(06):132-135.
[20] Eng L Z. Qt5 C++ GUI programming cookbook[M]. Packt Publishing Ltd, 2016.
[21] Pospelov G. Developing BornAgain graphical user interface: lessons learned[C]//Workshop on Neutron Scattering Data Analysis Software. 2018, 6: 8.
[22] 陶文玲,侯冬青. PyQt5与Qt设计师在GUI开发中的应用[J]. 湖南邮电职业技术学院学报,2020,19(01):19-21.
[23] 李飞. 基于C/S模式的图件管理系统的构建[J]. 科技视界,2019(27):39-41.
[24] 石珊. 云平台下基于FastDFS的文件管理系统的研究与实现[D]. 电子科技大学,2019.
[25] Bhandari A, Bhuiyan M, Prasad P W C. Enhancement of MD5 Algorithm for Secured Web Development[J]. JSW, 2017, 12(4): 240-252.
[26] Carlson J L. Redis in action[M]. Manning Publications Co., 2013.
[27] 张云, 许江淳, 李玉惠, 等. 基于 Nginx 服务器负载均衡技术的研究与改进[J]. 软件, 2017, 38(8): 6-12.
[28] 王宁. 基于分布式云平台视频存储及管理[D]. 中国石油大学(华东),2016.
[29] 孔德云. 基于FastDFS的大并发问题的研究与应用[D]. 中北大学,2017.
[30] Bourhis P, Reutter J L, Suárez F, et al. JSON: data model, query languages and schema specification[C]//Proceedings of the 36th ACM SIGMOD-SIGACT-SIGAI symposium on principles of database systems. 2017: 123-135.
[31] 杨丽波. 浅析集成测试和系统测试的关系[J]. 电子测试,2017(20):109-110.
[32] 范方政. 软件测试技术与缺陷跟踪管理的应用研究[D]. 吉林大学,2014.
[33] 朱健. 基于Qt Test的自动化单元测试[J]. 价值工程,2017,36(14):216-219.
曾经我也想过一了百了。
在疫情爆发的那段岁月里,每一天都是浑浑噩噩,一方面因为久久得不到学校方面的任何消息,使得我的精神饱受折磨;另一方面由于长期受到鼻炎的困扰,导致每天从睁开眼开始便是噩梦。就这样,一天天消磨着时间,每天都想做出改变,但每天都重复着昨天的过程。我常常在想,为什么上帝总是选择我,明明我只想安安稳稳,却总是被命运推着起起伏伏;明明想从今天起做出改变,但依旧重复着单调的生活。
那段时间,我总是把自己封闭起来,把所有情绪包裹在肚子里,不像任何人诉说心里的苦,即使对家人也不例外,因此家里人并不太清楚具体发生的事情。因此某天因为没有获得返校的资格而心中一肚子委屈,终于在吃饭时全部宣泄到了家人的身上。小时候每次发火,母亲总是忍耐我,而其他人骂我;但随着我的成长,懂得东西越来越多,家里人都知道说不过我,所以每次在我发火时,其他人反而总是忍耐我,只有母亲开始反驳我。我知道,母亲是怕我误入歧途,随着我的经历越来越多,没人再能管我,所以每次当我做错事或者宣泄情绪时,只有母亲站了出来。这一次所有人都开始劝我母亲不要再说下去,而我母亲却一直在指我错误的地方。终于当我母亲说理解我最近的心情,我说“你理解个屁”时,我母亲又一次哭了,或许是因为激动,或许是因为听到我受到委屈。那天,我上楼后,长期以来积在心里的委屈,终于以眼泪的形式散落出来。我躲在房间里哭的歇斯底里,或许因为委屈,或许因为母亲。
很长时间我常常想着如何没有痛苦地一了百了,但每当想起之后家人的眼泪,就打消了这个念头。有时候甚至抱怨,明明疫情把我的精神璀璨的支离破碎,为什么不让我患上肺炎,这样至少我便有了死去的理由,不用担心受到别人的谴责,或许这只是我想逃避一切的借口吧。
自疫情以来,我越来越喜欢睡觉,也越来越容易做梦。因为我发现当心情不好时,睡一觉什么都过去了,心情也好了,同时梦里的世界总是那么美好。某一天梦里,因为衣服脏了,我开始抱怨,结果母亲骂了我,随后因为意外我真的死去了。然后我以上帝视角看着一切,在我死后,我获得了想要的一切,我获得了所有的闪卡(可能和我玩的游戏有关),美国总统宣布我生前对世界的贡献(可能与每天的新闻报道有关),街头小巷也放映着我喜欢但不被大众接受的音乐(可能因为华晨宇得歌王后被大众黑),尽管我得到了一切,但是我已经死了,那一刻,我在呐喊,多希望上帝赋予我生命,哪怕一切化为乌有。我终于明白,当我死后我是多么希望我活下去。有时候不得不承认,梦虽然荒诞,像《爱丽丝梦游仙境》或者《红辣椒》一样,但却是我们潜意识真实的感受,像《夏洛特烦恼》,鼓舞我们继续向前,或许这一切都是上帝的指示,就像但丁的《神曲》。于是,我醒了,带着生的希望。
用漫不经心的态度,过随遇而安的生活。
虽然打消了死亡的念头,但生活中的无奈并未消退。曾经天天蹲教育厅,终于教育厅宣布可以开学了,于是蹲学校。然而当知道自己没办法去学校,而自己可能因为意外而面临失业时,犹如晴空霹雳,渐渐地,我不再去关注任何开学的消息,对生活也渐渐失去希望。某天夜里,我睁开了眼,却无法动,眼前一副鬼脸,一根棒子压着我,就像雷神的锤子落在洛基身上,施了魔法一样,明明意识想控制身体,但就是无法动。我清楚,这是魔鬼的考验,于是用尽全身的力气动了动手指,最终拜托了魔鬼的困扰。尽管这次成功了,但生活的烦恼越来越多。
后来发现了《与神对话》这部作品,于是一段时间生活又充满了阳光,开始了等待。然而迟迟等不到开学的消息,反而等来越来越多不好的消息。我开始埋怨,明明一个章的事情,结果拖了我三个月没有消息,眼前政府贪污,但至少有钱可以办事,现在政府领导们不接受贿赂,但也不愿承担风险,有钱也没办法。看着自己可能面临失业,我开始抱怨制度,甚至渐渐开始质疑上帝。某天梦里,我躺在床上,魔鬼抱起了我,而我却欣然接受了。然后我便醒了,我知道,这一刻,我已离上帝越来越远,渐渐变成了魔鬼。
然而事情总有解决的途径,上帝通过考验使人成长,但所有的苦难都是人们所能承受的。当困扰我三个月的问题有了解决途径时,我终于明白,其实之所以被困扰,完全是我自己困扰着我自己。每个人都只能通过自己的方式来认识世界,我们没办法感受别人的心情。也许在你看来很复杂的事情其实并没有那么复杂,在你看来很重要的事情别人可能并不了解情况,所有的困扰都是我们自己给自己带来的。当我们抱怨自己的命运在别人的一念之间,自己所有的努力都没有任何作用时,不妨多从别人的角度看待问题,多沟通,一切并不难解决。
在心门终于打开那一刻,我来到了天台,夕阳照在身上,群鸟飞翔,一切充满了生机。我知道,上帝不会放弃每一个人。
请逗留一刻吧,是那样美丽。
]]>服务端代码部署到虚拟机并启动
发现问题,Qt客户端注册与登录无响应
检查客户端登录注册代码,发现没有问题
检查客户端收到的错误码,发现客户端并未收到服务端反馈的错误码
查询服务端Nginx服务器log日志,发现80端口请求注册登录端口未成功
检查登录注册端口,发现未启动端口,排除客户端的问题
关闭防火墙,仍无法启动端口
重新启动服务端代码,查看服务端cgi程序的log日志,发现并没有log日志
spawn-fcgi启动报错提示spawn-fcgi: child exited with: 127
尝试执行 spawn-fcgi
命令 ,发现没有问题
1 | qianyouyou:~ $ spawn-fcgi |
执行cgi程序,发现问题No such file or directory
1 | # 找一下 libfcgi.so 位置在哪里。 |
依然提示No such file or directory
,重新make编译代码,提示找不到mysql/mysql.h
发现问题,虚拟机装了mysql-server,没有装mysql-devel,安装mysql-devel
1 | # Ununtu安装 |
make成功,重新启动成功,查看相应端口已启动
1 | netstat -ant | grep 10000 |
客户端登录注册失败,返回错误码,查询服务端log日志,发现没有相应数据库表
远程链接数据库,创建相应数据表
虚拟机服务端部署成功
代码部署到服务器,make同样提示没有mysql/mysql.h
安装mysql-devel未成功,提示冲突
1 | Transaction check error: file /usr/include/mysql/client_plugin.h from install of mariadb-1:5.5.41-2.el7_0.x86_64 conflicts with file from package MySQL-client-5.0.96-1.glibc23.x86_64 |
查阅yum安装如何避免冲突,删掉冲突文件,均没有用(后发现由于文件夹为软连接)
1 | # yum查看依赖库 |
由于已安装mysql为自定义文件夹,因此锁定问题为找不到自定义文件夹库
创建预编译链接,创建静态库动态库链接。查阅
1 | # 一次性命令 |
make成功,但除了login与upload外其余端口均为启动
执行login可执行文件,提示没有libmysqlcient.so.20
下载libmysqlclient.so.20,执行可执行文件,仍然报错
更换mysql版本为5.7
服务端重新启动,云盘服务端启动成功
客户端启动,可注册,不可登录
检查服务端代码,发现代码没问题
重新编译,提示没有任何文件改动,删除cgi所有.o文件,重新编译成功。
服务器云盘部署成功。
一、CentOS 7快速开放端口:
CentOS升级到7之后,发现无法使用iptables控制Linuxs的端口,baidu之后发现Centos 7使用firewalld代替了原来的iptables。下面记录如何使用firewalld开放Linux端口:
开启端口
[root@centos7 ~]# firewall-cmd –zone=public –add-port=80/tcp –permanent
查询端口号80 是否开启:
[root@centos7 ~]# firewall-cmd –query-port=80/tcp
重启防火墙:
[root@centos7 ~]# firewall-cmd –reload
查询有哪些端口是开启的:
[root@centos7 ~]# firewall-cmd –list-port
命令含义:
–zone #作用域
–add-port=80/tcp #添加端口,格式为:端口/通讯协议
–permanent #永久生效,没有此参数重启后失效
关闭firewall:
systemctl stop firewalld.service #停止firewall
systemctl disable firewalld.service #禁止firewall开机启动
二、CentOS6防火墙开放端口:
在我们使用CentOS系统的时候,CentOS防火墙有时是需要改变设置的。CentOS防火墙默认是打开的,设置CentOS防火墙开放端口方法如下:
打开iptables的配置文件:vi /etc/sysconfig/iptables
修改CentOS防火墙时注意:一定要给自己留好后路,留VNC一个管理端口和SSh的管理端口
下面是一个iptables的示例:
# Firewall configuration written by system-config-securitylevel
# Manual customization of this file is not recommended.
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:RH-Firewall-1-INPUT - [0:0]
-A INPUT -j RH-Firewall-1-INPUT
-A FORWARD -j RH-Firewall-1-INPUT
-A RH-Firewall-1-INPUT -i lo -j ACCEPT
-A RH-Firewall-1-INPUT -p icmp –icmp-type any -j ACCEPT
-A RH-Firewall-1-INPUT -p 50 -j ACCEPT
-A RH-Firewall-1-INPUT -p 51 -j ACCEPT
-A RH-Firewall-1-INPUT -m state –state ESTABLISHED,RELATED -j ACCEPT
-A RH-Firewall-1-INPUT -m state –state NEW -m tcp -p tcp –dport 53 -j ACCEPT
-A RH-Firewall-1-INPUT -m state –state NEW -m udp -p udp –dport 53 -j ACCEPT
-A RH-Firewall-1-INPUT -m state –state NEW -m tcp -p tcp –dport 22 -j ACCEPT
-A RH-Firewall-1-INPUT -m state –state NEW -m tcp -p tcp –dport 25 -j ACCEPT
-A RH-Firewall-1-INPUT -m state –state NEW -m tcp -p tcp –dport 80 -j ACCEPT
-A RH-Firewall-1-INPUT -m state –state NEW -m tcp -p tcp –dport 443 -j ACCEPT
-A RH-Firewall-1-INPUT -j REJECT –reject-with icmp-host-prohibited
COMMIT
修改CentOS防火墙需要注意的是,你必须根据自己服务器的情况来修改这个文件。
举例来说,如果你不希望开放80端口提供web服务,那么应该相应的删除这一行:
-A RH-Firewall-1-INPUT -m state –state NEW -m tcp -p tcp –dport 80 -j ACCEPT
全部修改完之后重启iptables:service iptables restart
你可以验证一下是否规则都已经生效:iptables -L
这样,我们就完成了CentOS防火墙的设置修改。
方法一:在编译自己的项目时添加-L和-I编译选项
1)添加头文件路径: -I #指明头文件的路径
2)添加库文件路径: -L #指定目录。link的时候,去找的目录。gcc会先从-L指定的目录去找,然后才查找默认路径。(告诉gcc,-l库名最可能在这个目录下)。 -l #指定文件(库名),linking options
注:-l紧接着就是库名,这里的库名不是真正的库文件名。比如说数学库,它的库名是m,他的库文件名是libm.so。再比如说matlab eigen库,它的库名是eng,它的库文件名是libeng.so。很容易总结得:把库文件名的头lib和尾.so去掉就是库名了。在使用时,“-leng”就告诉gcc在链接阶段引用共享函数库libeng.so。
方法二:将库路径添加到环境变量
1)添加头文件路径: 在/etc/profile中添加(根据语言不同,任选其一):
1 | export C_INCLUDE_PATH=C_INCLUDE_PATH:头文件路径 #c |
终端重启后需执行一次source。
另有一种方法:在/etc/ld.so.conf文件中加入自定义的lib库的路径,然后执行sudo /sbin/ldconfig
,这个方法对所有终端有效。
2)添加库文件路径:
1 | LIBRARY_PATH #used by gcc before compilation to search for directories containing libraries that need to be linked to your program. |
例如:
1 | MATLAB=/opt/MATLAB/R2012a |
题外话,顺便提一下LIBRARY_PATH和LD_LIBRARY_PATH的区别:
我们知道Linux下有2种库:static libraries和shared libraries。如(这里)阐述的,静态库是在编译期间会被链接并拷贝到你的程序中,因此运行时不再需要该静态库。动态库在编译时并不会被拷贝到你的程序中,而是在程序运行时才被载入,因此在程序运行时还需要动态库存在,这时就会用到LD_LIBRARY_PATH指定的路径去查找这个动态库。The libraries can be static or shared. If it is static then the code is copied over into your program and you don’t need to search for the library after your program is compiled and linked. If your library is shared then it needs to be dynamically linked to your program and that’s when LD_LIBRARY_PATH comes into play.
库文件在连接(静态库和共享 库)和运行(仅限共享库的程序,静态库会和可执行编译到一起)时被使用,其搜索路径是在系统中进行设置的。一般 Linux 系统把 /lib 和 /usr/lib 两个目录作为默认的库搜索路径,所以使用这两个目录中的库时不需要进行设置搜索路径即可直接使用。对于处于默认库搜索路径之外的库,需要将库的位置添加到 库的搜索路径之中。
设置库文件的搜索路径总的来说有以下几种:
在/etc/profile中添加如下环境变量。
编译时用到的环境变量:
1 | #gcc找到头文件的路径 |
运行时环境变量
1 | #程序运行时查找ku文件的路径 |
编辑mysql配置文件,把其中bind-address = 127.0.0.1
注释了。vi /etc/mysql/mysql.conf.d/mysqld.cnf
使用root进入mysql命令行,执行如下2个命令,示例中mysql的root账号和密码:root
1 | mysql -u root -p |
/etc/init.d/mysql restart
vi /etc/mysql/mysql.conf.d/mysqld.cnf
1 | mysql –u root -p |
/etc/init.d/mysql restart
1 | ...... |
1 | ...... |
宝塔是一个优秀的可视化服务器管理工具,提供了web操作面板,方便我们通过宝塔的web面板对服务器进行管理,例如
1、数据库安装、账号密码管理和数据管理 2、FTP账号的管理 3、各种服务器软件的安装,php、tomcat、nginx等 4、文件管理
宝塔的官网网站: http://www.bt.cn
通过宝塔进行安装 JPress,大概分为以下几个步骤:
1、购买服务器并安装宝塔 2、通过宝塔的后台面板安装nginx、Mysql和tomcat。 3、创建网站,并启用tomcat功能 4、上传JPress的war包,并解压缩 5、访问网站,走jpress自动安装过程
购买服务器建议购买阿里云的 centos 7.4 以上 ,里面不要安装其他任何功能(笔者在centos 7.2 下安装宝塔,nginx是无法使用的,centos 7.4 没问题)
安装宝塔非常简单,用 root 账号进入Linux服务,然后执行如下命令即可自动安装宝塔:
1 | yum install -y wget && wget -O install.sh http:// |
需要注意的是:安装的过程中控制台会打印安装的过程,在安装完成后,控制台会输出宝塔的登陆地址、账号和密码。
重要事情说三遍:
登陆地址、账号和密码,这部分务必要记住。 登陆地址、账号和密码,这部分务必要记住。 登陆地址、账号和密码,这部分务必要记住。
可以在宝塔的后台,通过 软件管理 > 运行环境
可以找到 nginx、mysql 和 tomcat。
点击安装即可。
需要注意的是各个软件的版本号:
在宝塔后台,通 网站 > 添加网站
创建一个新的网站。
创建网站的时候需要注意的是,创建mysql数据库的时候,版本要选择 utf8mb4
编码。
在宝塔后台的 网站
里,点击网站域名,在 tomcat
菜单里,启用 tomcat 功能。
在宝塔后台的 网站
里,点击根目录对应的目录链接,然后上传 jpress.war 到此目录。
因为宝塔无法对 .war 这种文件格式解压缩,所以需要重命名为 jpress.zip ,当然也可以在本地先把 jpress.war 先重命名为 jpress.zip 然后再上传也可以。
操作完成后,点击 jpress.zip 的解压缩即可。
访问你的域名,JPress自动引导进行安装,在JPress安装向导的过程中,只需要填写宝塔创建完毕的数据库账号和密码即可。
]]>问题:宝塔安装初次安装JPress无法正常启动?
答:
1、请查看下 /WEB-INF/classes 目录下是否有
jboot.porperties
和install.lock
这两个文件,如果有的话请删除。2、尝试重启 nginx 和 tomcat。
QObject::connect()
有五个重载: 1 | QMetaObject::Connection connect(const QObject *, const char *, |
由于做界面窗口最大化按钮时,第一次点击最大化窗口,第二次还原为原大小窗口,因此需要自己写一个函数。而为了避免代码冗余,于是在connect传参时使用了lambda表达式,代码如下:
1 | connect(ui->btn_group, &ButtonGroup::maxWindow, [=]() |
于是编译错误,提示
1 | no matching function for call to (类名)::connect()…… |
看到这个错误,首先想到的是参数问题,由于connect()参数为四个,虽然lambda表达式默认可以省略this参数,但还是添加上试试
1 | connect(ui->btn_group, &ButtonGroup::maxWindow, this, [=](){}); |
结果还是编译错误。后来上网查阅相关错误,结果大多数人的错误基本都是传参问题,但基本没有用lambda表达式的。于是删掉lambda表达式改用函数调用,结果编译通过。因此错误锁定在lambda表达式上。
后来发现,lambda表达式是C++11范畴,于是在pro文件当中添加以下代码:
1 | CONFIG += c++11 |
编译通过。
因此Qt使用lambda表达式时,首先使用的编译器要支持要用的C++11 特性,其次,在.pro中加上 CONFIG += c++11
服务器:Linux(CentOS 7.x,Ubuntu)
通过宝塔面板编译安装 Nginx 1.15
1 | sudo apt-get install nginx |
安装包:nginx-1.10.1.tar.gz
安装gcc g++的依赖库(如果没有gcc)
1 | sudo apt-get install build-essential |
安装pcre依赖库
1 | sudo apt-get update |
安装zlib依赖库
1 | sudo apt-get install zlib1g-dev |
安装SSL依赖库(16.04默认已经安装了)
1 | sudo apt-get install openssl |
关于configure、make、make install
1 | 源码的安装一般由有这三个步骤:配置(configure)、编译(make)、安装(make install) |
configure
1 | 首先检查机器的一些配置和环境,系统的相关依赖。如果缺少相关依赖,脚本会停止执行,软件安装失败 |
make
1 | make是Unix系统下的一个包。执行make命令需Makefile文件。make会根据Makefile文件中指令来安装软件 |
make install
1 | 当执行make命令不加任何参数,程序就会按照Makefile的指令在相应的section间跳转并且执行相应的命令 |
配置软链接
1 | sudo ln -s /usr/local/nginx/sbin/nginx /usr/bin/nginx |
现在就可以不用路径直接输入nginx启动。
配置开机启动服务
在/etc/init.d/下创建nginx文件,sudo vim /etc/init.d/nginx,内容如下:
1 | #!/bin/sh |
-
1 | #设置服务脚本有执行权限 |
现在基本上就可以开机启动了,常用的命令如下:
1 | sudo service nginx {start|stop|restart|reload|force-reload|status|configtest|rotate|upgrade} |
fastdfs-5.10.tar.gz
libfastcommon-1.36.zip
fastdfs-nginx-module_v1.16.tar.gz
参考
https://www.cnblogs.com/guigujun/p/7804670.html
本人使用的是阿里云服务器,配置上和上面教程略有不同
阿里防火墙 开放端口 TCP 22122 23000 等,记得开放要用的端口,否则后面操作失败
由于有了阿里云防火墙,所以服务器上的防火墙基本都不想开了,因为如果开放一个端口,要改阿里防火墙和服务器防火墙 2个地方,太繁琐了,所以可以使用以下命令禁用服务器自身的防火墙
1 | #关闭防火墙 |
使用软件上传所有FastDFS软件到服务器的 /home/package/fdfs目录上(目录自己建立)
1 | cd /home/package/fdfs |
1 | # 安装FastDFS |
1 | # 一并把后面要的所有目录创建 |
1 | ***************************** |
1 | vim /etc/fdfs/client.conf |
1 | fdfs_moniter /etc/fdfs/client.conf |
1 | # 图片 /home/temp/test.jpg |
1 | cd /home/package/nginx/nginx-1.10.1/ |
1 | ./configure --add-module=/home/package/fdfs_nginx_module/fastdfs-nginx-module/src |
1 | /www/server/panel/install/nginx.sh |
1 | /www/server/nginx/src/ |
1 | cd /www/server/nginx/src/ |
1 | nginx -V |
返回结果(我的)
1 | --user=www --group=www --prefix=/www/server/nginx --add-module=/www/server/nginx/src/ngx_devel_kit --add-module=/www/server/nginx/src/lua_nginx_module --add-module=/www/server/nginx/src/ngx_cache_purge --add-module=/www/server/nginx/src/nginx-sticky-module --with-openssl=/www/server/nginx/src/openssl --with-pcre=pcre-8.43 --with-http_v2_module --with-stream --with-stream_ssl_module --with-stream_ssl_preread_module --with-http_stub_status_module --with-http_ssl_module --with-http_image_filter_module --with-http_gzip_static_module --with-http_gunzip_module --with-ipv6 --with-http_sub_module --with-http_flv_module --with-http_addition_module --with-http_realip_module --with-http_mp4_module --with-ld-opt=-Wl,-E --with-cc-opt=-Wno-error --with-ld-opt=-ljemalloc --with-http_dav_module --add-module=/www/server/nginx/src/nginx-dav-ext-module |
1 | --add-module=/home/package/fdfs_nginx_module/fastdfs-nginx-module/src |
1 | --user=www --group=www --prefix=/www/server/nginx --add-module=/www/server/nginx/src/ngx_devel_kit --add-module=/www/server/nginx/src/lua_nginx_module --add-module=/www/server/nginx/src/ngx_cache_purge --add-module=/www/server/nginx/src/nginx-sticky-module --with-openssl=/www/server/nginx/src/openssl --with-pcre=pcre-8.43 --with-http_v2_module --with-stream --with-stream_ssl_module --with-stream_ssl_preread_module --with-http_stub_status_module --with-http_ssl_module --with-http_image_filter_module --with-http_gzip_static_module --with-http_gunzip_module --with-ipv6 --with-http_sub_module --with-http_flv_module --with-http_addition_module --with-http_realip_module --with-http_mp4_module --with-ld-opt=-Wl,-E --with-cc-opt=-Wno-error --with-ld-opt=-ljemalloc --with-http_dav_module --add-module=/www/server/nginx/src/nginx-dav-ext-module --add-module=/home/package/fdfs_nginx_module/fastdfs-nginx-module/src |
1 | ./configure --user=www --group=www --prefix=/www/server/nginx --add-module=/www/server/nginx/src/ngx_devel_kit --add-module=/www/server/nginx/src/lua_nginx_module --add-module=/www/server/nginx/src/ngx_cache_purge --add-module=/www/server/nginx/src/nginx-sticky-module --with-openssl=/www/server/nginx/src/openssl --with-pcre=pcre-8.43 --with-http_v2_module --with-stream --with-stream_ssl_module --with-stream_ssl_preread_module --with-http_stub_status_module --with-http_ssl_module --with-http_image_filter_module --with-http_gzip_static_module --with-http_gunzip_module --with-ipv6 --with-http_sub_module --with-http_flv_module --with-http_addition_module --with-http_realip_module --with-http_mp4_module --with-ld-opt=-Wl,-E --with-cc-opt=-Wno-error --with-ld-opt=-ljemalloc --with-http_dav_module --add-module=/www/server/nginx/src/nginx-dav-ext-module --add-module=/home/package/fdfs_nginx_module/fastdfs-nginx-module/src |
1 | fatal error: fdfs_define.h: No such file or directory |
vim 打开makefile目录下边的 objs/Makefile
在这个makefile中添加两个头文件
1 | vim /www/server/nginx/src/objs/Makefile |
直接make install
复制编译后产生的nginx文件,替换旧的nginx文件(自己需提前备份)
1 | cp /www/server/nginx/src/objs/nginx /www/server/nginx/sbin/ |
启动nginx, 只有一个master没有worker
查看日志文件
1 | /usr/local/nginx/logs/error.log |
1 | base_path=/home/fastDFS/storage #log日志目录 |
1 | location /group1/M00{ |
之前上传的图片路径为
1 | group1/M00/00/00/rBEx3l5k7cmAW6eiAAGyE0VtDsM290.jpg |
访问链接
http://39.96.209.253/group1/M00/00/00/rBEx3l5k7cmAW6eiAAGyE0VtDsM290.jpg
]]>有人说,我们降临到世上,要么是疯子,要么是傻子。疯子风雨兼程,义无反顾;傻子知足常乐,安之若素。其实这话说的也不错,就像人生的分岔路口上,都会有崎岖的山路,也会有平稳的坦途。选择山路,比较辛苦,随时都有跌落山谷的危险,但沿途能欣赏广阔的风景;选择坦途,比较轻松,不用思考任何风险,但也失去了广阔的风景。就像每个人都会问自己,是想成为马云,还是想成为佛祖。每个人都会有自己的选择,谁也没有资格评论其他人的选择,唯一能做的,就是不忘初心,坚持自己的选择。
我时常会问自己,究竟是选择山路,还是选择坦途。很多时候,我都会犹豫不决,一方面是对挫折的恐惧,一方面又不甘于碌碌无为。但这个时候,上帝总会帮我寻找心中所向往的那片风景。秋招时,因为恐惧面试,而草草的签了三方协议。尽管薪资达不到自己的预期,但还是选择了安稳。本以为从此之后便再也没办法回头,但还是遇到了人生的伯乐,经过心理上克服面试的恐惧,最终成功签约自己心仪的公司。
当我们历经磨难,踏上山巅时,已经欣赏到了更远阔的风景,也收获了沿途克服崎岖的喜悦。但我们的人生没有暂停键,也没有后退键。这时候,我们依旧要向前走,但当我们到达一座山顶时,四周的路只能向下。而山那边,依然是山。我们别无选择继续跨越前方的山。就像我,因为签约了新的公司,必须想办法解约三方协议,而我们作为违约的一方,就必须承担责任,想获得学校一方的同意挺难,因为有很多流程,谁也不想自己太麻烦;以前的公司不会简简单单的帮助我们违约,违约不违约都是要交违约金,而且没违约成功,未来在旧公司里也会备受排挤。而此刻的我,没有回头路,只能继续向前。
当我们选择山路时,往往也会向往安稳的坦途,但身在山路,稍不留神便会跌落谷底。与其后悔,不妨继续享受这沿途的风景。我们之所以害怕山路,其实是对挫折的恐惧。就像我对与学校和公司违约谈判的恐惧,对这个过程中可能遇到的各种麻烦一样。但是任何没有打倒我们的力量,只能让我们的内心变得更强大。因此,此刻正望着另一座山的我,定要把挫折当做使自己变得强大的力量,战胜恐惧,欣赏风景,坚定向前。就像汪国真《热爱生命》中写的那样:
我不去想是否能够成功
既然选择了远方
便只顾风雨兼程
我不去想能否赢得爱情
既然钟情于玫瑰
就勇敢地吐露真诚
我不去想身后会不会袭来寒风冷雨
既然目标是地平线
留给世界的只能是背影
我不去想未来是平坦还是泥泞
只要热爱生命
一切,都在意料之中
影片分为了四个小故事,背景分别在当代,文革时代,战火时代,以及那个咆哮的时代。通过职场,爱情,家国,自我救赎,四个主题,以救赎为牵引线,化作了一个故事,共同传达了爱与传承的主旨。
电影首先讲述的是生活在当代尔虞我诈的职场,每天愁眉苦脸的男主张果果,因为上司的设计而背黑锅被迫离职。在新的公司里,因为看到曾经能帮助而未尽力帮助的四胞胎家庭如今生活落魄,决定通过事业帮助这个家庭。在医院看望四胞胎婴儿时,他向婴儿家人承诺会争取四胞胎的手术费。而在对他们提供帮助后,前公司的老总告诉他一些过往的经历劝他小心。他开始怀疑四胞胎的家人从此以后赖上了他。而同时老总给男主提供了一个揭发曾经算计他的上司丑陋罪行的机会。面对心中善恶的审批,男主该如何回应呢。
第二个故事发生在文革前。黄晓明饰演的陈鹏是一个大学的优等生,章子怡饰演的王敏佳与铁政饰演的李想则是医学院的一个学生,三人从小便是相依相伴的孤儿。陈鹏一直喜欢着王敏佳,原本打算为爱放弃事业时,却看到王敏佳和李想两个人暧昧,选择了离开。王敏佳和李想看到师母殴打恩师,写信为恩师打抱不平,却遭到师母的告状。面对李想的前程,王敏佳选择独自承担,却遭到了各个阶级莫须有的罪名,李想自始至终选择沉默。最终在批判大会上含冤死去。
第三个故事讲述的是飞行家的故事,王力宏饰演的沈光耀原本是富家子弟,文武兼备,因为一腔热血,报名空军,却遭到家人的反对,最终选择放下功名,安之若素。之后却看着日军狂轰乱炸,在国与家的面前,他该何去何从。
第四个故事比较简单,由陈楚生饰演的吴岭澜国学与英语成绩优异,然而当时正值实业救国思潮,本是文科好苗子的他选择了理工科,然而却因物理成绩垫底被迫重新作出选择。对于自己所擅长的文科与实用的理科,他该如何选择。
最终,吴岭澜听了泰戈尔的演讲,明白了那些伟人以及引领时代的人其实不一定都是实业家,他们其实时时刻刻都在对生命的价值进行思索。最终他毅然遵从内心,选择了文科,从此当一名教师,传道受业解惑。
而正是吴岭澜的几句话,影响了沈光耀,在日军轰炸同胞,战火迷离之际,他选择了当一名飞行员,为保家卫国献上了自己最绚烂的青春。 在他执行飞行任务时,总不忘将食物从空中撒向贫民区。从自己的村子,到飞机途径的各个村子,都有他所传递的那份爱。
正因为沈光耀的选择,才救助了陈鹏他们所在的村子,造就了陈鹏一村人感恩的心。最终陈鹏为爱返乡,将濒死的王敏佳带回村子,托付给村子救助。因为陈鹏的守护,王敏佳才有了生的希望。
在陈鹏的感染下,李想最终在大雪纷飞的雪地里将食物留给了素不相识的张果果的父母,最终自己却冻死了。而每年,张果果一家不管多忙碌,总会在清明节那天去为李想扫墓。正是因为这份爱与传递,最终感染了张果果,他最终洗涤了心中世俗化的感染,无偿为四胞胎的父母提供了住所与工作,也主动选择放弃揭发曾经算计自己的上司。
满怀热忱的我们,无可避免都得经受现实的洗礼。
还记得王敏佳在批判大会上的那抹笑容。当她环顾四周,心中所盼望的两个男人统统不在,周围全是乌合之众的谩骂,她笑了。正是因为这一抹笑,引来了群众的不满,由谩骂变成了毒打,使王敏佳付出自己的生命。看到这里,我首先想到的是《鬼子来了》中,姜文最后人头落地时的仰天长笑。那三声长笑,看似荒诞,但正是在嘲讽这荒诞的世界,这没有正义,只有灰色的世界。
在理想与现实的十字路口上,我们该何去何从。
影片中,张果果和老总形成了鲜明的对比,是理想与现实的对比。老总在抱怨他曾经帮助过的孩子最终却向自己要房子要工作,而张果果听了老总的劝告,经历了思想斗争之后,却选择主动为四胞胎家庭提供住所与工作。看到这里,我想到了《我不是药神》中程勇由最初为了利益卖药到最后以德报怨主动倒贴钱卖药给穷人的转变。这是一种救赎。能力越大,责任就越大。张果果身为广告公司总监,主动选择将爱传递给那些需要帮助的人,这也是一种救赎。但我们也不能全怪老总,毕竟老总曾经也想过帮助别人,只是没有坚持下去,他所得到的是社会的一次鞭挞,从此被世俗同化,做一条狼。在理想与现实的十字路口,多少人因为社会的一次次的鞭挞而选择向现实妥协,将中心转向了自我,而又有几个人会像道成肉身的耶稣那样以德报怨,选择自己被钉上十字架来替世人赎罪呢。
当我们选择负重前行时,谁来为我们护航呢。
在王敏佳环顾四周,心中充满期盼,却又孤立无援的时候,我又想到了《闻香识女人》,查理在遭到同学的冷眼旁观与学校的威逼利诱之时,在自己的前途与心中的道义选择之时,遵从内心,坚守正义与准则。但不同于查理,查理在被勒令退学,心中充满迷茫之时,有老中校的保护,而王敏佳此刻却一无所有。这让我不禁感叹,其实我们每个人都曾满怀热忱选择拥抱这世界,但当我们被这灰色社会各种折磨之时,谁来像老中校那样保护我们的坚守呢。最终大部分人都被这社会所同化,变成了自己曾经最讨厌的那种人。影片毕竟不是悲剧,所幸剧情最终安排陈鹏回到了王敏佳身边,守护王敏佳对这世界最后的一丝好感。
每个人都认为自己是对的,因此我们需要成长。
沈光耀原本是富家子弟,却选择当一名飞行员保家卫国,同时救济贫民。但刚开始时,他每次都是擅自开飞机为自己学校所在的村子送食物。他的上司罚他,同时也道出了原因,他这样做,看似义举,但每次同样的路线,只会引来敌人的追踪,最终导致村子遭到敌人的狂轰乱炸。看到这里,我不仅揪起了心。我们每个人其实刚开始时都认为自己是对的,就像王敏佳只看到了师母殴打恩师的表象,却忽略了恩师对师母的冷落,因此她将师母抹黑,遭到师母的告发。而同样因为师母的一句话,群众们又谩骂变成毒打,险些让王敏佳丧命。显然,师母当时因为嫉妒与愤怒,导致一个人前程与生命被毁。她其实也是无心之过,也是被这世界摧残的遍体鳞伤,不然也不会在最后在内疚与绝望中选择跳井自杀。有时候,我们自己的一点点微不足道的私欲或自以为是的正义可能会给其他人带来不可弥补的伤害。
成长中的负重前行,方得始终。
最终沈光耀的故事算是最完美的结局。他后来不断改变路线,给村子驱赶饥饿的同时保证了村子的安全。同时,将小义变成了大义,所到之处都有他的带来的食物。人们亲切地称他为晃晃叔叔。
当影片《奇异恩典》想起,我便明白,正值迷茫之际,此电影以救赎之道,为我曾经人生十字路口的选择保驾护航。
]]>已知类a继承类b且包含类c,求a,b,c的构造顺序。
b > c > a
这个答案大家肯定知道,但为什么呢,对于该问题一般那些资深C++开发者一眼就能看出你是有自己的理解还是书本式的记忆,因此不要试图用书本式的解释。
其实构造函数也没那么神秘,就是用来初始化数据,为对象的创建做准备的。这不废话么。有了这个认识,那为什么上面是bca,而不是其他顺序呢。好像没什么必然的联系。但是,以初始化为主的构造函数,那没有数据的话怎么初始化数据呢。因此,a包含c,那么c就是a数据的一部分,没有c的话a就无法初始化。因此c > a。a继承于b,子类和父类的关系根据名称也知道,子类继承了父类的数据,父类也是子类的一部分,因此b > a。abc这三者的关系就好似儿子、父亲和儿子的细胞。没有父亲,就没有儿子的全部,自然也没有儿子的细胞。所以答案自然而然。
直接初始化>初始化列表>构造函数
在C++11特性中,出现了RAII的思想,就是构造中分配内存,析构中释放内存。这也是智能指针保证安全性的原因。
]]>首先,什么是分布式存储,简而言之,就是将数据存储到多个存储设备(服务器)上。
传统的网络存储系统采用集中的存储服务器存放所有数据,存储服务器成为系统性能的瓶颈,也是可靠性和安全性的焦点,不能满足大规模存储应用的需要。
分布式网络存储系统采用可扩展的系统结构,利用多台存储服务器分担存储负荷,利用位置服务器定位存储信息,它不但提高了系统的可靠性、可用性和存取效率,还易于扩展。
FastDFS是一款开源的分布式文件系统,通过纯C实现,支持Linux, FreeBSD等Unix系统类Google FS, 不是通用的文件系统,只能够通过专有API访问,目前提供了C,Java和PHP API为互联网应用量身定做,解决大容量文件存储问题,追求高性能和高扩展性 FastDFS可以看做是基于文件的key-value存储系统,称为分布式文件存储服务更为合适。
功能包括:文件存储、文件同步、文件访问(文件上传、文件下载、文件删除)等,解决了大容量存储和负载均衡的问题。
为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标
可以很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。
fastDFS特点:
fastDFS框架中的三个角色
fastDFS三个角色之间的关系
Client和Storage主动连接Tracker。
Storage主动向Tracker报告其状态信息
Storage会启动一个单独的线程来完成对一台Tracker的连接和定时报告。
一个组包含的Storage不是通过配置文件设定的,而是通过Tracker获取到的。
Tracker集群
Storage集群
FastDFS的扩容分纵向扩容与横向扩容
纵向扩容
当前组的最大容量
所有存储节点组名必须一样
我的方法是采用位运算,也就是二进制表示状态,0001表示第1个数,0010表示第2个数,0100表示第3个数,1000表示第4个数,0011表示第1个数和第2个数间所有的操作,同理1111就表示4个数之间所有的操作,1111可以有1100与0011进行加减乘除操作得到,也可以通过1011和0100得到……我们最后只需看1111里面是否有24即可(保存步骤的话需要自定义结构体,既添加字符状态)。
我们用set容器来存储当前状态下的所有结果(去重),那么set[1]即0001就是保存第一个数,set[2]即0010就是保存第二个数,set[3]即0011就是保存第一个数和第二个数的所有结果……某set可由其他两set的数据得出(例如0011可由0010和0001得到),则每次依次遍历其他两个set中所有元素再将新的结果插入到当前set中,这样从1遍历到15即可。
通过列真值表,00=0,01=0,10=1,11=0,我们可以得到a与!b即可得到想要的结果c,例如1011与!0010得到1001,当然c必须保证c不等于a且c不为0。而为了剪枝,我们也可以让c小于b。因此可以添加以下条件:
1 | int k = (i & (~j)); |
两个数之间加减乘除共有6种结果,a+b,a-b,b-a,a * b, a / b, b / a,因此我们将相应结果存入set(集合为了去重,如果需要详细步骤则可用其他容器,不必用set,因为步骤需要保存状态)。
1 | for(it = m_set[j].begin(); it != m_set[j].end(); it++){ |
这样我们就可以通过4层循环(因为只有4个数嘛,n范围也就是4)完成24点的判断了。以下是详细代码。
1 | #include<bits/stdc++.h> |
另附原始递归代码:
1 | #include<bits/stdc++.h> |
但是当我们只为了完成当日任务,而不为长远维护考虑,那未来将是一件可怕的事情。而且那样的代码,像我这样的人看着也不舒服。
最近看了C#高级语法,赫然发现C#中的委托不就是C++中的函数指针嘛。于是,对于上述if-else,便有解了。
我们只需要写一个委托,对应创建一个数组对象,每个跳转类型封装一个方法,再用委托来回调,这样的代码,主方法里只需1行就搞定。
1 | using System.Collections; |
有没有发现,和设计模式中的工厂方法模式有异曲同工之妙呢。日后维护起来也方便许多。
接下来我们详细介绍下C#中的委托吧。
C# 中的委托(Delegate)类似于 C 或 C++ 中函数的指针。委托(Delegate) 是存有对某个方法的引用的一种引用类型变量。引用可在运行时被改变。
委托(Delegate)特别用于实现事件和回调方法。所有的委托(Delegate)都派生自 System.Delegate 类。
委托声明决定了可由该委托引用的方法。委托可指向一个与其具有相同标签的方法。
例如,假设有一个委托:
1 | public delegate int MyDelegate (string s); |
上面的委托可被用于引用任何一个带有一个单一的 string 参数的方法,并返回一个 int 类型变量。
声明委托的语法如下:
1 | delegate <return type> <delegate-name> <parameter list> |
一旦声明了委托类型,委托对象必须使用 new 关键字来创建,且与一个特定的方法有关。当创建委托时,传递到 new 语句的参数就像方法调用一样书写,但是不带有参数。例如:
1 | public delegate void printString(string s); |
下面的实例演示了委托的声明、实例化和使用,该委托可用于引用带有一个整型参数的方法,并返回一个整型值。
1 | using System; |
当上面的代码被编译和执行时,它会产生下列结果:
1 | Value of Num: 35 |
委托对象可使用 “+” 运算符进行合并。一个合并委托调用它所合并的两个委托。只有相同类型的委托可被合并。”-“ 运算符可用于从合并的委托中移除组件委托。
使用委托的这个有用的特点,您可以创建一个委托被调用时要调用的方法的调用列表。这被称为委托的 多播(multicasting),也叫组播。下面的程序演示了委托的多播:
1 | using System; |
当上面的代码被编译和执行时,它会产生下列结果:
1 | Value of Num: 75 |
下面的实例演示了委托的用法。委托 printString 可用于引用带有一个字符串作为输入的方法,并不返回任何东西。
我们使用这个委托来调用两个方法,第一个把字符串打印到控制台,第二个把字符串打印到文件:
1 | using System; |
当上面的代码被编译和执行时,它会产生下列结果:
1 | The String is: Hello World |
还记得没来上海时,身边回荡着各种声音,上了年纪的长辈们鼓励我年轻人就应该出去闯一闯;父母则是抱着忧虑的念头,总说大城市生活压力大,骗子多,无时无刻都得防备;朋友们相对而言更担心的是我一个人出远门的安全。那时候,心中荡漾的,不清楚是期待,还是恐惧。
初来上海时,东奔西跑的找房租房,生活上高昂的消费让自己不得不开始学会记录支出,还有工作上初来乍到的各种不适应,总弄得自己身心疲惫;那时候,为最后一个挤上公交地铁而庆幸,为了早起而不得不早睡的无奈。心中徘徊的,应该是厌倦,还有抱怨吧。
现在呢,发现人们口中的大城市,并没有像人们所描述的那么繁华,也没有人们所描述的那样让人苦不堪言。渐渐地,我发现自己爱上了这里。
其实啊,上海没变,世界也没变,只有我们的心态变了。
就像人们议论程序员这个行业,一提到程序员,最多的话题就是996,也就是朝九晚九,每周6天。听到这词,即使没有工作,也难免会让人感觉疲惫。但是,真的如此吗。就像我,工作一段时间,感觉每天都很充实。每周有两天双休,但每次到了双休,玩一天游戏,或者什么也不干,剩下的只有空虚,所以周末能加班就加班,因为加班使自己感到生活的充实与价值。
还记得刚来学校时,因为热爱编程,而报了计算机专业。那时候,没日没夜敲代码是我的快乐。快找实习时,也是没日没夜敲代码,只是感觉到的是苦涩。而工作一段时间后,当我又开始忙碌地敲代码时,感觉到的是充实。同样是敲代码,在学校时因为热爱,在找实习时因为迷茫和环境的影响而决定苦涩,现在,是找到了方向。所以,所谓的忙碌,只不过是旁观者的看法。当一个人真正热爱自己的工作时,别人不要求你加班,你也想自己去加班。
人们都说上海节奏快,但我坚信时代造英雄。
昨天父亲又给我打了个电话,尽管只是平常地问候,但我却草草结束了话题。我后来发现,渐渐地,我越来越不怎么和父亲说话了。因为每次父亲跟我洽谈,总是告诫我一些关于社会让人多么的无奈,或者大城市节奏太快压力太大,亦或是社会上到处都是坏人,即使同一屋檐下也要上一把锁。这并不是我父亲一个人的想法,而是我们那个小城镇普遍的想法。
受我父亲的影响,刚上大学时,因为父亲说西安是贼城,当时我坐个公交都得把包背到前面。但久而久之,我发现并不是所有人都是坏人,那些不美好只不过是世界的冰山一角,但更多地是世界的美好,许多人身上都是有光的。只是这冰山一角给人的心理带来了防备,但当我们以这冰山一角来定义世界时,将必定错过整个世界。
2G时代,将互联网带上中国,成就了搜狐、新浪、网易。3G时代,将互联网带到了生活,成就了百度、阿里、腾讯,号称BAT。4G时代,将互联网带到了人们随身携带的手机,成就了头条、美团、滴滴,号称TMD。如今5G时代的到来,一切皆有可能,如果还停留在旧时代的舒适圈,不肯加快脚步,那么城乡差距就会越来越大。
放宽心态,多看看世界好的一面,那么哪来那么多烦恼呢。
如今,我每周周末都会去公寓周围的大超市去逛逛,有时候什么也不买,仅仅是因为里面的音乐让自己舒心。感受了一周的充实,偶尔给自己放个小假,你会发现,生活并不单调。
追随时代的大步伐,难免会疲惫。偶尔停下脚步,慢下来,感受自己心中所聆听到的声音,你会发现,快节奏的生活,也会有慢下来的情调。
]]>我不知道,
风在往何处吹,
吹散了无根的枝叶,
还有七月的蔷薇。
我不知道,
风会往何处吹。
吹向那紫陌的寂寥,
还是霓虹的余辉。
我不知道,
风该往何处吹。
吹散那远方的清梦,
还是脚下的尘灰。
我不知道,
风向往何处吹。
吹向了温存的故事,
还有光阴的慈悲。
我不知道,
我什么也不知道。
也许只有风儿知道方向,
那我便化作海浪随风依洄。
也许风儿也不知道方向,
那我将像那尘埃何处可归。
1、男女性别比例
2、各省好友数量
3、个性签名云图
一个商业级图表,纯Js图表库。用于生成Echarts图表的类库。
中文分词组件。
工程设计的Python工具包。包括统计、优化、整合、线性代数模块、傅里叶变换、信号和图像处理、长积分方程求解器等等。
Python词云展示库,在一段文本中提取关键词进行扁平化的展示,更能吸引目标客户的眼球。
微信个人API
Python的re模块提供了诸多正则表达式模块,使Python拥有了全部的正则表达式功能,库1方便检查一个字符串是否与某种模式匹配。
1、登录
bot = Bot() #扫描二维码自动登录
2、获取登录账号的所有好友
bot.friends()
3、获取当前登录账号所关注的公众号
bot.mps()
4、获取当前登录账号群聊列表
bot.groups()
5、搜索好友:
bot.friends().search(‘好友备注名’)[0]
6、搜索好友并发送消息
bot.friends().search(‘好友备注名’)[0].send(‘str’)
7、向文件传输助手发送信息
bot.fle_helper.send(‘str’)
1、每个键值(key:value)对用冒号分隔
1 | >>>s = {'name':'udbs', 'age':20} |
字典名[‘key’]
字典名[‘key’] = ‘value’
1、删除单一元素
格式:del 字典名[‘key’]
2、清空所有元素
格式:字典名.clear()
3、删除字典
格式:del 字典名
open函数,必须先用Python内置的open()函数打开文件,创建一个file对象。
1 | file object = open(file_name[, access_mode][, buffering]) |
try-finally语句可以处理异常。但较麻烦。
python提供了with语句语法,来构建对资源创建与释放方法,但功能与try相似。with语句后面跟着open方法,如果有返回值,可以使用as语句赋值给变量,退出时自动调用close。
re.findall()方法
搜索整个字符串,返回匹配正则表达式的所有内容。
1 | re.findall(pattern,string) |
re.compile()方法
将正则字符串编译成正则表达式对象,以便后期匹配中复用。
1 | re.compile('正则表达式') |
join()方法
用于将序列中的元素以指定的字符连接生成一个新的字符串
精确模式:jieba.lcut(str)
试图将语句最精确切分,不存在冗余数据,适合文本分析。
全模式:jieba.lcut(str,cut_all=True)
将与句中所有可能是词的词语都切分出来,速度快,但存在冗余数据
搜索引擎模式:jieba.lcut_for_search(str)
在精确模式的基础上·,对长词再次进行切分。
1 | >>> str = '手持两把锟斤拷' |
功能:判断该列元素是否在某一个列表中。是True,否False。
agg基于列的聚合操作
groupby基于行
]]>1 课题简介
1.1 背景及研究现状
在互联网高速发展的时代背景下,网络正以一种前所未有的冲击力在影响着人类的活动,包括人类的生产和日常生活。网络的诞生和发展,颠覆了传统的信息传播方式,冲破了存在于传统交流方式中时间和空间的种种壁垒,极大地改变了人类从物质到精神、从形式到内容、从生产到生活的各种活动,并且给人类带来了新的机遇和挑战。网络购物作为电子商务的一种形式正以其高效、低成本的优势,逐步成为新兴的经营模式和理念,人们已经不再满足用途信息的浏览和发布,而是渴望着能够充分享受网络所带来的更加多的便利。网络购物正适应了当今社会快节奏地生活,使顾客足不出户便可以方便快捷轻松地选购自己喜欢的商品。
本次网上购物系统基于课程设计以专业综合设计I的设计成果,对其进行Web服务器端功能的扩展与开发,将其设计成一个具有服务器端交互功能的完整的Web应用系统,并完成系统的测试、服务器端的部署与发布。
1.2 设计内容与设计思路
网上购物系统主要是对后台管理和前台操作。后台管理是管理员对本网站的维护,通过商品资料(商品添加、商品修改),等功能达到对网站的管理。前台操作是用户登录到本网站,可以进行用户注册,通过网站的热门商品推荐或商品分类查找功能实现商品搜索,找到自己想要买的商品,装入购物车,提交定单进行购买。
网上购物平台的特点是客户和电子商品信息量很大,管理员需要整理的信息很多,为让管理员轻松、方便、快捷的管理,该平台采用符合购买电子商品基本的原则,满足广大客户的日益增长的数量,并达到操作过程中的直观、方便、实用、安全等要求。
1.3 设计目的及意义
网上购物系统,是在网络上建立一个虚拟的购物商场,使消费者的购物过程变得轻松、快捷、方便,很适合现代人快节奏的生活;同时又有效的控制“商场”运营的成本,开辟了一个新的销售渠道。
网上购物相较于实体店铺有着其独特的优势,首先相较于实体店铺网上店铺所需要的成本很小很多,网上购物不需要考虑门店等等的费用其次网上购物系统不存在店铺打样的问题延长了经营的时间无需专人看店节约成本的同时还能使得效益更大化。
本系统利用现代化的电子及网络技术,为消费者和企业搭建一个良好的互动平台。让用户享受快捷的购物方式,为企业提供不同于传统销售的崭新的销售模式。 该购物系统是一个中小型的电子商务系统,可以为各类用户提供方便的在线购物环境。用户可以在系统中实现注册、登录、修改个人信息、分类查询商品信息、购物、管理购物车、支付等功能。管理员可以通过后台管理模块实现商品的增删改查、种类的增删改查等,从而实现对于该购物系统的管理。
2 系统分析与设计
2.1 可行性分析
可行性研究是为了弄清楚系统开发的项目是不是可以实现和值得进行研究的过程,实际上是一次大大简化系统分析和系统设计的过程,所以,进行可执行性的分析是非常必要的,也是很重要的,可行性研究阶段通过对系统目标进行市场调研和技术分析,提出了初步的可行性方案并进行了论证。这里主要从技术可行性、经济可行性以及操作可行性三方面进行分析。
2.1.1 经济可行性
该系统硬件只需要一台PC机,而且配置要求不高。软件方面用到的MySQL是免费开源的,Eclipse也是免费使用的,所以开发成本并不高。而该系统若投入运行之后不仅减少了人力、物力而且可以推动电影行业的发展,所带来的收益是巨大的,因此在经济上也是可行的。
2.1.2 技术可行性
技术可行性要考虑到现有的技术手段和能力能不能完成系统的开发,以免开发到一半出现现有技术无法解决的问题。本系统主要采用的是JAVAWeb和JSP技术。JAVA语言和JSP都是很成熟的技术,也是世界范围内应用最广泛的技术,所以用它们作为后台和前台的语言是没有任何问题的。而且集成开发工具Eclipse可以解决很多方面的问题,创造了良好的开发环境。由于JSP功能强大,而MySQL灵活易维护在开发方面具有方便快捷、使用灵活的特点,以及目前的广泛实际应用,因此使用JAVAWed、MySQL是开发轻平台的最佳组合从而说明本系统在技术方面可行。
硬件方面,科技飞速发展的今天,硬件更新的速度越来越快,容量越来越大,可靠性越来越高,价格越来越低,其硬件平台完全能满足此系统的需要。
2.2 需求分析
2.2.1 系统设计目标
此网络购物平台系统旨在为用户提供一个简易的具备一些基本功能的购物系统,通过这个系统用户可以轻松的获得自己想要的商品。存在两种类型用户,普通用户和管理员用户。普通用户在这个系统中注册过后进行登陆此时可以在平台中进行自由的选购自己想要的商品,将选购好的商品加入购物车,如果需要修改可以在购物车中更改所需的商品数量,当选择完成过后可以确认购买,也可以让所选物品一直处在购物车中,等待下次登陆时依然有效。管理员用户可以对商品进行管理,包括商品上架,商品下架,商品查询,商品修改等。
2.2.2 系统功能需求分析
(1)用户管理:注册会员、登录、管理员用户校验、激活、退出;
(2)商品显示:按分类查询商品、通过首页推荐查询商品、展示热门商品、展示最新商品、提交商品到购物车中等;
(3)购物车管理:向购物车中添加商品、修改购物车中商品数量、删除购物车中商品、我的购物车;
(4)订单管理:通过购物车中商品生成订单、查看我的订单、查看某个订单的详细、订单支付、确认收货。
(5)管理员:订单查询、商品和类别的增删改查。
2.2.3 系统性能需求分析
用户注册部分需要实时检验用户信息是否合格,否则用户提交后才检验,若不合格需重新填写,用户很快会厌倦,因此Ajax是不错的选择。
商品部份应该给用户提供推荐,比如最新商品和最热商品,给用户提供访问便利。
此外,分类部分不常改,且使用频繁,但每次若从数据库中读取会影响效率,因此使用redis缓存数据是个不错的选择,因为大大加快了访问效率。
订单部分由于支付功能需要第三方平台,所以可不必实现。
管理员部分需要对订单具有全权访问权限。
2.3 系统总体设计
2.3.1 系统总体结构设计
本项目采取MVC设计模式,分为model,view,controler三层。本项目包含dao,domain,service,web,utils五打包。
其中web包中包含各种servlet类和前端jsp进行交互,及view层。
Service为业务逻辑层,控制信息的权限以及信息在数据库与前端的交互,及controler层。Dao中主要负责与数据库进行交互,通过各种sql获取或添加信息到数据库,及model层。
Domain为基本类,共三大层创建对象进行层与层间的信息交互,作为信息载体的集合而存在。
Utils包中包含各种工具,例如数据库中的date类型需要string进行转换,或者id的生成,这些工具均包含于utils包中。基本模式如下图:
图2-1 系统总体结构设计图
B/S系统架构,简单点就是用户通过访问浏览器输入域名后,转入对应的前端html或jsp,然后通过http或https协议将信息报文传送给服务端,服务端进行相应之后做出一系列对数据或页面的响应。以下是B/S系统架构图。
图2-2 B/S系统框架图
2.3.2 系统功能模块设计
(1)用户注册功能:发送邮件、激活用户、表单的校验、用户的登录功能、自动登录、用户的注销功能
(2)商品模块:首页热门商品和最新商品功能、商品分类、商品的列表(分页)、商品详细信息、浏览记录功能
(3)购物车模块:将商品加入购物车、展示购物车功能
(4)订单模块(多表和事务):提交订单、展示订单、在线支付
(5)后台的分类的模块:分类信息的增删改查
(6)后台的商品模块:商品的信息的增删改查(文件上传)
(7)后台的订单的模块:所有的订单的信息展示
2.3.3 主要业务流程
用户的注册过程:
是首先进入主界面,点击注册按钮,跳转到注册页面,填写注册信息,ajax判断信息是否合格,然后跳转到注册servlet层,把信息传递给service层,service层通过dao层获取数据库信息,反馈是否成功给servlet层,最终按是否插入成功而跳转到注册成功或失败界面。之后介绍的流程大致如此,及MVC三层模型的传输过程。
图
2-3 程序包结构图
主界面显示:
首先index.jsp加载head.jsp,然后获取最热商品信息与最新商品信息,并显示在主界面,最后加载footer.jsp。
Head.jsp界面显示:
首先加载首页,然后访问redis数据库判断是否有种类信息,有则读取,无则访问MySQL数据库,读取数据后放到redis中。
用户注册分析:
图2-4 用户注册详情
商品设计:
图2-5 商品详情
通过浏览器访问商城,就相当于超市访问商品,因此我们需要购物车来装载商品以便对所需物品增删改查。
图2-6 购物车设计
订单业务设计:
图2-7 订单业务设计
管理员管理流程:
图2-8 管理员管理
2.4 系统数据库设计
2.4.1 数据库概念模型设计
图2-9 E-R图
2.4.2数据库逻辑结构设计
表2-1 category表结构
Column Name | Date Type | Width | 空值情况 |
---|---|---|---|
Cid | varChar | 50 | 主关键字 |
Cname | varChar | 20 | 默认为空 |
表2-2 orderitem表结构
Column Name | Date Type | Width | 空值情况 |
---|---|---|---|
itemid | varChar | 50 | 主关键字 |
count | int | 11 | 可为空 |
subtotal | double | 0 | 可为空 |
pid | varchar | 50 | 可为空 |
oid | varchar | 50 | 可为空 |
表2-3 order表
Column Name | Date Type | Width | 空值情况 |
---|---|---|---|
oid | varChar | 50 | 主关键字 |
ordertime | datetime | 0 | 可为空 |
total | double | 0 | 可为空 |
state | int | 11 | 可为空 |
address | varchar | 30 | 可为空 |
name | varchar | 20 | 可为空 |
telephone | varchar | 20 | 可为空 |
uid | varchar | 50 | 可为空 |
表2-4 product表
Column Name | Date Type | Width | 空值情况 |
---|---|---|---|
pid | varChar | 50 | 主关键字 |
pname | Varchar | 20 | 可为空 |
Market_price | double | 0 | 可为空 |
Shop_price | double | 0 | 可为空 |
pimage | varchar | 30 | 可为空 |
pdate | date | 20 | 可为空 |
Is_hot | int | 20 | 可为空 |
pdesc | varchar | 255 | 可为空 |
pflag | int | 11 | 可为空 |
cid | varchar | 50 | 可为空 |
表2-5 user表
Column Name | Date Type | Width | 空值情况 |
---|---|---|---|
uid | varChar | 50 | 主关键字 |
username | Varchar | 20 | 可为空 |
password | Varchar | 20 | 可为空 |
name | varchar | 20 | 可为空 |
varchar | 30 | 可为空 | |
telephone | varchar | 20 | 可为空 |
birthday | date | 0 | 可为空 |
sex | varchar | 10 | 可为空 |
state | int | 11 | 可为空 |
code | varchar | 50 | 可为空 |
2.4.3 数据库关系设计
图2-10 数据库关系图
3 系统详细设计
3.1 系统开发及运行环境
3.1.1 软件环境
运行环境:Windows操作系统下eclipse
开发语言:JavaWeb
数据库:MySQL,redis
3.1.2 硬件环境
阿里云服务器。
3.2 系统采用的关键技术
3.2.1页面显示逻辑与业务逻辑相分离
MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC被独特的发展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构中。
3.2.2数据库连接与访问
C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。
3.2.3 Ajax异步校验
通过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。传统的网页(不使用 Ajax)如果需要更新内容,必须重载整个网页页面。
3.3 系统框架的实现
本项目采取MVC设计模式,分为model,view,controler三层。本项目包含dao,domain,service,web,utils五大包。
其中web包中包含各种servlet类和前端jsp进行交互,及view层。
Service为业务逻辑层,控制信息的权限以及信息在数据库与前端的交互,及controler层。Dao中主要负责与数据库进行交互,通过各种sql获取或添加信息到数据库,及model层。
Domain为基本类,共三大层创建对象进行层与层间的信息交互,作为信息载体的集合而存在。
Utils包中包含各种工具,例如数据库中的date类型需要string进行转换,或者id的生成,这些工具均包含于utils包中。
3.3.1 主要的类与接口
表3-1 JSP清单
Jsp | 说明 |
---|---|
admin/home.jsp | 管理员界面 |
index.jsp | 主界面 |
head.jsp | 头界面 |
footer.jsp | 尾界面 |
Product_list.jsp | 商品栏界面 |
Product_info.jsp | 单项商品信息界面 |
order_list.jsp | 订单栏界面 |
order_info.jsp | 订单项界面 |
cart.jsp | 购物车界面 |
login.jsp | 登陆界面 |
register.jsp | 注册界面 |
表3-2 MVC模式清单
Servlet | Service | Dao | Utils | Domain |
---|---|---|---|---|
AutoLoginFilter | AdminService | AdminDao | BeanFactory | Cart |
ActiveServlet | ProductService | ProductDao | CommonsUtils | CartItem |
AdminServlet | UserService | UserDao | DataSourceUtils | Category |
BaseServlet | AdminServiceImpl | JedisPoolUtils | Order | |
CallbackServlet | MailUtils | OrderItem | ||
PrdocutServlet | MD5Utils | PageBean | ||
RegisterServlet | PaymentUtil | Product | ||
UserServlet | User |
3.3.2 系统主要配置文件
c3p0-config.xml负责数据库连接配置。
redis.properties负责redis端口属性配置。
Bean.xml配置AdminServiceImpl的清单。
Web.xml配置jsp清单。
3.4 具体功能模块的实现
3.4.1前台分类信息展示
在head.jsp中有以下js代码:
图3-1 主页head栏
通过Ajax方式,首先访问CategoryServlet中的findCategory()方法,查询并以json格式返回Category的List集合db.cname即是最终导航栏显示的分类信息。
点击某一个分类后,会访问ProductServlet中的findPage()方法,传递当前页数:1和分类ID:cid。
3.4.2分类商品的分页展示
图3-2 分类商品首页图
Dao层查询每页显示的数据(select * from product where cid = ? limit ?,?),Service层调用Dao后再进行条数页数的设置并封装进javabean,返回给servlet。
Servlet中的findPage()方法:
首先获取分类的ID:String cid = request.getParameter(“cid”);
然后获取当前页数:
int pageNumber =Integer.parseInt(request.getParameter(“pageNumber”));
之后设定每页显示的条数,带着参数调用service。将返回的PageBean对象和分类ID存储,供jsp页面使用。
Service层的findPage()方法:
List
封装的每页显示数据,按总条数和总页数进行封装。然后pageBean按每页显示的数据封装数据。
Servlet将pagebean存到request域中后,在jsp页面通过jstl遍历展示商品信息:
图3-3 产品分类显示
图3-4 产品分类jsp代码
3.4.3单个商品详情
在商品列表页面有:
<ahref=”${pageContext.request.contextPath}/product?method=productList&cid=${cid }¤tPage=${currentPage}”>
这是定义在每个商品图片上的超链接,点击后访问ProductServlet中的findBypid()方法,查询单个商品的详情。
图3-5 产品单件商品信息页面
3.4.4最新和热门商品展示
首先,每个商品有一个是否热门属性和一个上架时间属性。加载主页面时,会从数据库中读取is_hot为1的所有商品作为热门商品,并加载时间最近的商品作为最新商品。href为${pageContext.request.contextPath}/product?method=productInfo&pid=${hotPro.pid}。项目运行后直接访问ProductServlet中的findByNew()方法,查询最新最热商品信息,然后跳转到/jsp/index.jsp页面进行展示。
图3-6 热门商品展示
图3-7 最新商品展示
4 系统测试
4.1 系统测试方法
测试方法采用了边界值,场景分析,等价类,用jmeter进行接测和压测。
4.2 系统测试用例
表4-1 系统测试
测试功能 | 测试用例序号 | 测试样例 |
---|---|---|
按类别查询商品首页 | 1 | 直接查询 |
分页展示分类商品 | 2 | 按种类添加删除商品信息 |
单个商品信息校验 | 3 | 修改部分商品信息 |
最热与最新商品展示 | 4 | 修改最热属性以及日期 |
4.3 系统测试结果
表4-2 测试结果
测试用例序号 | 测试结果 |
---|---|
1 | 成功 |
2 | 成功 |
3 | 成功 |
4 | 成功 |
5 总结
5.1 系统工作总结
我们这次首先应用到了MVC框架一开始使用的时候还有点生疏,但是在熟悉了以后,MVC框架的优点现了出来,它把前后台分开进行处理,我们在写后台逻辑的时候不需要考虑前端网页的布局,在写前端网页的时候也不需要在页面中插入后端的业务逻辑,这样让整个开发的过程变得十分明确,包括在查看源码的时候也不会眼花缭乱,当代码发生错误时,调试起来也能够快速的发现错误点,这是相较于之前没有框架使用的时候所没有的优越之处。同时,我们这次还用了C3P0的数据库池连接,它的优势之处就是比原本单纯的JDBC连接更加快捷,速度更快了。
5.2 存在的不足及改进
本系统由于涉及到支付功能,而支付又涉及到现金交易,因此该部分一直没有实现,经查阅资料了解到第三方支付平台的存在,但目前由于第三方支付平台账号问题目前支付功能具体还没有实现。之后会陆续对该部分进行改进。
]]>unity pro 2019破解版,其除了原程序还附带了破解补丁和许可文件,能够免费帮助用户破解得到一个可无限制免费使用所有功能的unity pro 2019,随后在下文会附上破解安装教程。
unity2019破解版安装说明
1、下载并解压本站提供的包,其包换了unity pro 2019原程序、破解补丁、许可文件。
2、先打开文件夹”addons”双击”UnityHubSetup-1.6.2.exe”依提示进行安装Unity Hub。
3、这里默认路径为【C:\Program Files\Unity Hub】
4、耐心等待安装完成后先不要运行该软件。
5、然后双击”UnitySetup64-2019.1.2f1.exe”依提示进行安装开始安装Unity。
6、这里默认路径为【C:\Program Files\Unity】
7、这里有点慢,请耐心等待安装。
8、安装完成后同样先不要运行该软件。
9、然后将本站提供的文件夹”crack”——“Hub”中的”app.asar”拖至【C:\Program Files\Unity Hub\resources】中并选择复制和替换即可。
10、接着选择”crack”进入到”Unity”选择用户自己需要的版本。
11、例如小编这里选择2019.1.0f2,那么就将文件夹里面的”Unity.exe”拖至【C:\Program Files\Unity\Editor】中并选择复制和替换即可。
12、然后将”2019.1.0f2”文件夹中的”Unity_lic.ulf”许可文件复制到【C:\ProgramData\Unity】中,如果C:驱动器没有ProgramData文件夹,则启用隐藏文件显示。是C:\ProgramData文件夹中没有Unity文件夹,那么就创建一个。
13、运行”Unity Hub”点击”Locate a Version”
14、找到unit.exe文件的路径即【C:\Program Files\Unity\Editor】点击”select sditor”
15、接着需要创建一个用于验证的新项目,点击”new”
16、安装所需的支持模块和附加组件。
17、耐心等待即可,到这里就已经全部破解完成了,用户就可以免费无限制使用unity pro 2019破解版了。
]]>用户注册功能
发送邮件
激活用户
表单的校验
用户的登录功能
自动登录
用户的注销功能
首页热门商品和最新商品功能
商品分类
商品的列表(分页)
商品详细信息
浏览记录功能
将商品加入购物车
展示购物车功能
提交订单
展示订单
在线支付
分类信息的增删改查
商品的信息的增删改查(文件上传)
所有的订单的信息展示
1)确定项目需求—–拿下一个项目
2)编写《需求说明书》—-不涉及技术,只涉及业务需求
3)编写《概要设计说明书》—– 涉及技术的的宏观的内容,数据库设计,页面原型
4)编写《详细设计说明书》—– 相当于伪代码
5)编码阶段coding—-根据《详细设计说明书》— 单元测试
6)联测—–项目组内部的行为
7)测试组进行全面的专业测试—-《测试报告》
8)上线(测试阶段)
9)维护和二次开发
(1) 创建项目Shop
(2) 创建项目的包结构
(3) 导入需要的jar/配置文件/工具/静态页面
(4) 编码
基本点注册代码实现
分析:表单提交数据—->web层收集数据—->封装数据—–>传递数据—>三层架 构代码
邮箱中的链接
点击时 访问服务端进行激活功能的ActiveServlet
前台对表单已经进行进行校验了,后台好需要对数据进行校验吗?—-需要!
现在要求你戳破所有的气球。每当你戳破一个气球 i 时,你可以获得 nums[left] * nums[i] * nums[right] 个硬币。 这里的 left 和 right 代表和 i 相邻的两个气球的序号。注意当你戳破了气球 i 后,气球 left 和气球 right 就变成了相邻的气球。
求所能获得硬币的最大数量。
说明:
你可以假设 nums[-1] = nums[n] = 1,但注意它们不是真实存在的所以并不能被戳破。
0 ≤ n ≤ 500, 0 ≤ nums[i] ≤ 100
示例:
输入: [3,1,5,8]
输出: 167
解释: nums = [3,1,5,8] –> [3,5,8] –> [3,8] –> [8] –> []
coins = 315 + 358 + 138 + 181 = 167
思路:
可以利用区间动态规划,dp[i][j]表示i到j之间的最优解(不包括i,j),那么dp[i][j]就等于max(dp[i][j], dp[i][k] + dp[k][j] + nums[i] * nums[j] * nums[k])。
即我们假设求i到j之间的最优解,k为i和j之间的数,那么当前i到j之间以k为基准将要戳k(也就是k是i到j中最后一个戳的)的最优解就等于k左半部分最优解加k右半部分最优解加k、j、i的乘积,遍历k求出最大的一个就好了。当然dp初始值要为0,这样第一次戳i时dp[i-1][i+1]就理所当然等于0 + nums[i+1] nums[i] nums[i - 1] + 0。
nums首部先插入1,再在末尾补1。
1 | class Solution { |
C为面向过程语言,C++与C不是对立关系,而是包容关系。C++不仅包含面向过程的C,还可以面向对象,也可以泛型编程。简而言之,C++分为面向过程,面向对象,泛型编程模板,STL标准模板库四部分。
作用域运算符,全局作用域直接加::
用途 解决名称冲突问题
using std :: X,使用某变量或对象。如果该部分作用域已经存在同样的名称对象,则会产生二义性而报错。
using namespace X,使用命名空间。跟编译器说的。
const int a = 10;不分配内存,只在编译器符号表中。
const int a = b;分配内存
C++默认const内链接,C外链接即默认extern。
const有作用域,有类型。define无作用域,无类型
算符有顺序,例如a·=b和b·=a不一样。
普通算符无顺序,例如a<b和b>a一样。
将输入符号串a1a…an#依次逐个存入符号栈S中,直到遇到栈顶符号ai的优先性·>下一个带输入符号aj为止。
栈顶当前符号ai为句柄尾,由此向左在栈中找句柄的头符号ak,即找到ak-1<·ak,为止。
由句柄ak…ai在文法产生式中查找右部尾ak…ai的产生式,若找到则用左部代替句柄,若找不到则为出错,断定不合法。
重复1,2,3.直到只剩开始符为止。
和简单优先分析相比仅有终结符才能有优先级比较。其余优先符关系同于上式。
FIRSTVT(B) = {b | B +⇨ b…或B +⇨ Cb…}
LASTVT(B) = {a | B +⇨ …a 或B +⇨ …aC}