一、系统概要说明
这次项目做到是一个简单的图片网站,就是将自己的图片发出来共享和保存使用的。并且也能浏览其他用户上传的图片,并能进行评论、点赞和收藏的操作。
除了一些基本的用户注册和登陆功能、投稿发布的功能、组合搜索等功能,还有分类显示功能、通过点赞和收藏数展示热门图片和推荐作品、用户头像和用户密码修改功能、图片上传时的图片预览功能、个人主页展示全部作品和收藏评论等功能。
本次系统开发使用的是python的flask框架,我使用了pycharm作为开发工具。作为一个近几年流行起来的语言,flask算是python语言中比较容易上手的一种框架。
二、网站结构设计
本网站一共设置了四大模块,展示模块、搜索模块、作品模块和用户模块。
1、作品展示
这里的作品展示模块,包括了首页展示、热门图片展示、推荐作品展示、分类图片展示四个页面。
首页展示包括了顶部导航和底部导航、页面头部的滚动栏以及下方的所有作品展示。
热门图片包括了顶部和底部导航、头部的广告展示栏和主要的热门图片展示区域。
推荐图片和热门图片相同,只是展示的为推荐的图片。
分类展示是根据点击了不同的下拉选项,从而显示选择的类型的图片。
2、搜索功能
这里的搜索功能是模糊查找,通过在导航栏中的搜索框输入进行搜索。
可以根据作品的标题来查找,也可以根据作者的名字查找,也可以根据分类名称进行查找。
3、作品模块
作品模块包括进行作品的投稿、对作品进行评论、对作品进行收藏以及对作品进行点赞等四个功能
投稿功能可以进行图片的上传,并能在投稿页面中预览上传的文件,并且填写作品名称和选择图片类型。
评论功能即用户可以对作品进行评论,并显示在作品详细页下面,会显示用户的头像、发布时间以及评论内容。
收藏和点赞功能较为雷同,每个用户都能对任意作品进行点赞或收藏,但是每个用户只能对同一个作品点赞或者收藏一次,不可以重复收藏和点赞,同时也就有取消收藏和点赞功能。并且会记录每个作品的收藏和点赞次数,收藏则可以在个人中心找到自己的收藏。
这里设置了需要用户登陆才能对作品进行以上操作。
4、用户模块
用户模块包括用户的登陆和注册、个人中心的展示、头像的修改和密码的修改。
登陆和注册是最基本的创建用户和查询用户的操作。
个人中心展示包括了全部投稿作品、全部收藏、全部评论和用户信息四个栏目。显示了该作者的作品、收藏和评论等信息。
修改头像功能,用户可以对自己头像进行修改,在修改页面可以预览自己上传的头像,并进行上传和跳转。
修改密码功能,用户只有在点进自己的个人中心才能选择修改密码,并且通过输入旧密码和对比两次新密码,从而对用户的密码进行修改。
三、模块详细设计
1、展示模块
a、首页展示
主py文件中通过对投稿表的遍历,将值传递给首页,而首页则用for循环将所有作品都展示出来。
这里的首页html中上半部分为首页的滚动栏,下半部为遍历的for循环。
主py文件:
@app.route('/') def index(): context={ 'touGao':Tougao.query.order_by('-time').all() } return render_template("index.html",**context)
首页html:
<div class="container"> <div class="row clearfix"> <div class="header"> <div class="banner"> <div class="banner_wrap"> <ul class="banner_img clear_fix"> <li class="ig"><img src="{{ url_for("static",filename="Img/banner1.jpg") }}" alt=""></li> <li class="ig"><img src="{{ url_for("static",filename="Img/banner2.png") }}" alt=""></li> <li class="ig"><img src="{{ url_for("static",filename="Img/banner3.png") }}" alt=""></li> <li class="ig"><img src="{{ url_for("static",filename="Img/banner4.jpg") }}" alt=""></li> </ul> <div class="banner_left"> <img src="{{ url_for("static",filename="Img/向左.png") }}" alt=""> </div > <div class="banner_right"> <img src="{{ url_for("static",filename="Img/向右.png") }}" alt=""> </div> </div> </div> </div> <div class="main"> <ul> {% for item in touGao %} <div class="imgbox"> <div class="img"><a href="{{ url_for('digital',tougao_id=item.id) }}"><img src="{{ url_for("static",filename="upload/"+item.picturename) }}"></a></div> <div class="title"><p>{{ item.title }}</p></div> <div class="msg"> <span><img src="{{ url_for("static",filename="Img/赞.png") }}">{{ item.zan}}</span> <span><img src="{{ url_for("static",filename="Img/_收藏.png") }}">{{item.collection}}</span> </div> </div> {% endfor %} </ul> </div>
首页JS文件(滚动栏自动变换及上下页):
var i=0; var timer; $(window).ready(function () { $('.ig').eq(0).show().siblings('.ig').hide(); showTimer(); $('.banner_left').click(function () { clearInterval(timer); i--; if(i==-1){ i=3; } Show(); showTimer() }) $('.banner_right').click(function () { clearInterval(timer); i++; if(i==4){ i=0; } Show(); showTimer() }) }); //会动的定时器 function showTimer() { clearInterval(timer) timer=setInterval(function () { Show(); i++; if(i==4){ i=0; } },2500) } //会动的方法 function Show() { $('.ig').eq(i).fadeIn(300).siblings('.ig').fadeOut(300); }
b、热门图片展示
主py文件:
@app.route('/hotpic') def hotpic(): context={ 'hotTouGao':Tougao.query.order_by('-zan').all() } return render_template("hotpic.html",**context)
2、搜索模块
主py文件:
@app.route("/search")#搜索方法 def search(): q=request.args.get("q") testuser=User.query.filter(User.username.contains(q)).all()#注意这里返回的是list类型 tes1=User()#设置一个空User对象 testuserid=[]#设置一个list用于存储包含关键字的用户的id for i in range(len(testuser)):#遍历包含关键字的User用户的列表 tes1=testuser[i] #将list列表里的每个用户转成User对象进行使用 testuserid.append(tes1.id)#将User的id存起来 # 同理获得类别 testleibie=Fenlei.query.filter(Fenlei.fenleiname.contains(q)).all() test2=Fenlei() testfenleiid=[] for i in range(len(testleibie)): test2=testleibie[i] testfenleiid.append(test2.id) tg=Tougao.query.filter( or_( Tougao.title.contains(q), Tougao.userid.in_(testuserid),#判断userid是否在列表中 Tougao.fenleiid.in_(testfenleiid) ) ).order_by("-time") return render_template("index.html",touGao=tg)
c、分类图片展示
主py文件:
#分类显示页面 @app.route('/list/<fenlei_id>') def list_fenlei(fenlei_id): tougao=Tougao.query.filter(Tougao.fenleiid==fenlei_id).all(); context={ 'tougao':tougao } return render_template('fl_list.html', **context)
导航html:
<li id="dropd"><a href="#">图片类型</a> <ul class="show_list"> <li><a href={{ url_for("list_fenlei",fenlei_id=1) }}>人物</a></li> <li><a href={{ url_for("list_fenlei",fenlei_id=3) }}>自然风光</a></li> <li><a href={{ url_for("list_fenlei",fenlei_id=6) }}>动物</a></li> <li><a href={{ url_for("list_fenlei",fenlei_id=10) }}>设计元素</a></li> </ul> </li>
3、作品模块
a、投稿功能
主py文件:
@app.route('/tougao',methods=['GET','POST']) @log def tougao(): if request.method=='GET': context = { 'leiBie': Fenlei.query.all() } return render_template('tougao.html', **context) else: file_dir = os.path.join(basedir, app.config['UPLOAD_FOLDER']) if not os.path.exists(file_dir): os.makedirs(file_dir) f = request.files['pic1'] # 从表单的file字段获取文件,file为该表单的name值 if f and allowed_file(f.filename): # 判断是否是允许上传的文件类型 fname = secure_filename(f.filename) ext = fname.rsplit('.', 1)[1] # 获取文件后缀 unix_time = int(time.time()) new_filename = str(unix_time) + '.' + ext # 修改了上传的文件名 title = request.form.get('tougaotitle')#标题 leibie = request.form.get('group')#分类名称 id = User.query.filter(User.username == session.get('user')).first().id#用户id fenleiid=Fenlei.query.filter(Fenlei.fenleiname == leibie).first().id#分类id tougao = Tougao(title=title,userid=id,picturename=new_filename,fenleiid=fenleiid) db.session.add(tougao) db.session.commit() f.save(os.path.join(file_dir, new_filename)) # 保存文件到upload目录 return redirect(url_for('index')) else: return jsonify({"errno": 1001, "错误信息": u"文件类型错误"}) # return "From docker: {}".format(output.strip()) return output
投稿html代码:
<div class="panel panel-default"> <h2>投稿图片</h2> <div class="panel-body"> <form class="form-horizontal" role="form" action="{{ url_for('tougao') }}" method="post" enctype=multipart/form-data> <div class="form-group"> <label for="tougaotitle" class="col-sm-2 control-label">投稿标题</label> <div class="col-sm-10"> <input type="text" class="form-control" id="tougaotitle" name="tougaotitle"> </div> </div> <div class="form-group"> <label for="inputfile" class="col-sm-2 control-label">上传图片</label> <div class="col-sm-10"> <div id="container"></div> <input type="file" name="pic1" id="pic1" onchange="preview(this)" multiple="multiple" accept="image/x-png, image/jpg, image/jpeg, image/gif"> <br> </div> </div> <div class="form-group"> <label for="toutaotext" class="col-sm-2 control-label">投稿简介</label> <div class="col-sm-10"> <select name="group" id="group"> <option>-请选择-</option> {% for item in leiBie %} <option>{{ item.fenleiname }}</option> {% endfor %} </select> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <button type="submit" class="btn btn-default">提交</button> </div> </div> </form> </div> </div>
首页JS:
<script> var msg = "您可以上传png, jpg, 或者gif格式的图片"; var filter = { "jpeg": "/9j/4", "gif": "R0lGOD", "png": "iVBORw" }; function preview(file) { var container = document.getElementById("container"); container.innerHTML = ""; if (window.FileReader) { for (var index=0, f; f = file.files[index]; index++) { var filereader = new FileReader(); filereader.onload = function (event) { var srcpath = event.target.result; if (!validateImg(srcpath)) { console.log("H5"+msg); } else { showPreviewImage(srcpath); } }; filereader.readAsDataURL(f); } } else { if (!/\.jpg$|\.png$|\.gif$/i.test(file.value)) { console.log("原生"+msg); } else { showPreviewImage(file.value); } } } function validateImg(data) { console.log(data); var pos = data.indexOf(",") + 1; for (var e in filter) { if (data.indexOf(filter[e]) === pos) { return e; } } return null; } function showPreviewImage(src) { console.log(src); var img = document.createElement('img'); img.src = src; img.style = "width:400px;height:auto;" container.appendChild(img); } </script>
b、评论功能
主py文件:
@app.route('/pinglun',methods=['POST']) @log def pinglun(): pl=request.form.get('pingluntext') tougao_id=request.form.get('tougaoid') user_id=User.query.filter(User.username==session.get('user')).first().id pinglun=Pinglun(userid=user_id,tougaoid=tougao_id,content=pl) db.session.add(pinglun) db.session.commit() return redirect(url_for('digital',tougao_id=tougao_id))
c、收藏及点赞功能
主py文件(这里以点赞作为示例,收藏代码和点赞大致相同):
#点赞方法 @app.route('/_dianzan') def dianzan(): a = request.args.get('a', 0, type=int) b = request.args.get('b', 0, type=int)#投稿ID _tougao = Tougao.query.filter(Tougao.id == b).first() c = User.query.filter_by(username=session.get('user')).first().id#用户ID #判断用户是否已经点赞 user_zan=Dianzan.query.filter(Dianzan.userid==c,Dianzan.tougaoid==b).first() if user_zan:#已经点赞,要取消点赞 result = a - 1#点赞数减一 db.session.delete(user_zan)#删除点赞表中的该用户点赞记录 _tougao.zan=result db.session.add(_tougao) db.session.commit() sta = '-1' else:#没有点过赞,要点赞 result = a + 1 user_zan=Dianzan(userid=c,tougaoid=b)#新增用户点赞记录 db.session.add(user_zan) _tougao.zan=result db.session.add(_tougao) db.session.commit() sta = '+1' return jsonify(result=result,sta=sta) #详情页页面刷新时通过ajax判断显示样式 @app.route('/_fresh') def shuxin(): b = request.args.get('b', 0, type=int) # 投稿ID c = User.query.filter_by(username=session.get('user')).first().id # 用户ID # 判断用户是否已经点赞 user_zan = Dianzan.query.filter(Dianzan.userid == c, Dianzan.tougaoid == b).first() # 判断用户是否已经收藏 user_shou = Shoucang.query.filter(Shoucang.userid == c, Shoucang.tougaoid == b).first() if user_zan: # 已经点赞,要取消点赞 sta = '-1' else: # 没有点过赞,要点赞 print('点赞') sta = '+1' if user_shou: # 已经点赞,要取消点赞 sta2 = '-1' else: # 没有点过赞,要点赞 print('点赞') sta2 = '+1' return jsonify(sta=sta,sta2=sta2)
点赞html代码:
<div class="_praisebox"> <div class="_praise" id="praise"> <span class="imgbox zan oncur" id="zan_one"></span> <span id="praise-txt" class="_praise-txt">{{ quest.zan }}</span> </div> </div>
点赞JS代码:
$(function() { $('#praise').bind('click', function() { $.getJSON($SCRIPT_ROOT + '/_dianzan', { a: $('span#praise-txt').text(), b:$('#digital_id').text(), c:$('#digital_author_id').text(), }, function(data) { $("#praise-txt").text(data.result); if (data.sta=='-1'){ $("#zan_one").removeClass("imgbox yizan oncur"); $("#zan_one").addClass("imgbox zan oncur"); }else if (data.sta=='+1') { $("#zan_one").removeClass("imgbox zan oncur"); $("#zan_one").addClass("imgbox yizan oncur"); } }); return false; }); }); #页面初始化(判断是否已经点赞,从而给出不一样的图标) $(function () { $.getJSON($SCRIPT_ROOT + '/_fresh',{ b:$('#digital_id').text(), c:$('#digital_author_id').text(), },function(data){ if (data.sta=='+1'){ $("#zan_one").removeClass("imgbox yizan oncur"); $("#zan_one").addClass("imgbox zan oncur"); }else if (data.sta=='-1') { $("#zan_one").removeClass("imgbox zan oncur"); $("#zan_one").addClass("imgbox yizan oncur"); } }); });
4、用户模块
a、登陆及注册功能(以登陆为例)
@app.route('/login',methods=['GET','POST']) def login(): if request.method == 'GET': return render_template('login.html') else: username = request.form.get('id') # 与html页面名字相同 password = request.form.get('password') user = User.query.filter(User.username == username).first() if user: if user.check_password(password): session['user']=username session.permanent=True return redirect(url_for('index')) else: return '密码错误' else: return '用户不存在'
b、个人中心展示功能
@app.route('/author/<user_id>/<tag>') def author(user_id,tag): user=User.query.filter(User.id==user_id).first() soucang=Shoucang.query.filter(Shoucang.userid==user_id).all() context={ 'user':user, 'tougao':user.tougao, 'pinglun':user.pinglun, 'soucang':soucang } if tag=='1': return render_template('author1.html', **context) elif tag=='2': return render_template('author2.html', **context) elif tag=='3': return render_template('author3.html', **context) elif tag=='4': return render_template('author4.html', **context)
html文件(分为四个板块,分别继承同一个模板,这里列出子页):
baseauthor:
<div class="container"> <div class="row clearfix divcolor"> <div class="col-md-8 column"> <div id="content"><img src="{{ url_for("static",filename="upload/"+user.userpic) }}"></div> <div class="information"> <h2>{{ user.username }}</h2> <p>投稿总数:{{ tougao|length }} 评论数量:{{ pinglun|length }}</p> {% if username == user.username%} <div> <a href="{{ url_for('upload') }}">修改头像</a> <a href="{{ url_for('changepas') }}">修改密码</a> <div class="clear"></div> </div> {% endif %} </div> <div class="clear"></div> <hr> <ul class="nav nav-pills" role="tablist"> <li class="active"><a href="{{ url_for('author',user_id=user.id,tag=1) }}">全部投稿</a></li> <li><a href="{{ url_for('author',user_id=user.id,tag=4) }}">全部收藏</a></li> <li><a href="{{ url_for('author',user_id=user.id,tag=2) }}">全部评论</a></li> <li><a href="{{ url_for('author',user_id=user.id,tag=3) }}">用户信息</a></li> </ul> <br> {% block author %}{% endblock %} </div> <div class="col-md-4 column"> </div> </div> </div>
第一个子页author1:
<ul> {% for item in tougao %} <div class="imgbox"> <div class="img"><a href="{{ url_for('digital',tougao_id=item.id) }}"><img src="{{ url_for("static",filename="upload/"+item.picturename) }}"></a></div> <div class="title"><p>{{ item.title }}</p></div> </div> {% endfor %} </ul>
c、修改头像功能
主py文件:
# 跳转到上传页面 @app.route('/upload',methods=['GET'],strict_slashes=False) def upload(): return render_template("upload.html") # 用于判断文件后缀 def allowed_file(filename): return '.' in filename and filename.rsplit('.',1)[1] in ALLOWED_EXTENSIONS # 上传文件 @app.route('/upload',methods=['POST'],strict_slashes=False) def api_upload(): file_dir = os.path.join(basedir, app.config['UPLOAD_FOLDER']) if not os.path.exists(file_dir): os.makedirs(file_dir) f = request.files['pic1'] # 从表单的file字段获取文件,file为该表单的name值 if f and allowed_file(f.filename): # 判断是否是允许上传的文件类型 fname = secure_filename(f.filename) ext = fname.rsplit('.',1)[1] # 获取文件后缀 unix_time = int(time.time()) new_filename = str(unix_time)+'.'+ext # 修改了上传的文件名 # 修改数据库中的图片名称 user1 = User.query.filter_by(username=session.get('user')).first() user1.userpic=new_filename db.session.commit() f.save(os.path.join(file_dir, new_filename)) #保存文件到upload目录 return render_template('testresult.html') else: return jsonify({"errno": 1001, "errmsg": u"failed"}) #return "From docker: {}".format(output.strip()) return output
d、修改密码功能
主py文件:
#修改密码的方法 @app.route('/changepas',methods=['GET','POST']) def changepas(): if request.method == 'GET': return render_template("changepas.html") else: _password = request.form.get('password') # 与html页面名字相同 _newpassword = request.form.get('newpassword') user1 = User.query.filter_by(username=session.get('user')).first() if user1:#判断用户是否存在 if user1.check_password(_password):#判断密码是否正确 user1.password = _newpassword#修改密码 db.session.commit() return render_template('testresult.html')#返回个人主页 else: return '密码错误' else: return '用户不存在'
四、数据库设计
本次项目一共有六个表,分别是用户表、投稿表、评论表、点赞表、收藏表和分类表。
用户表(User)
字段名 | 表示内容 | 字段类型 | 备注 |
id | 主键 | 整型 | 自动生成 |
username | 用户名 | 字符型 |
|
password | 密码 | 字符型 | 已加密的密码 |
userpic | 用户头像图片名称 | 字符型 |
|
投稿表(Tougao)
字段名 | 表示内容 | 字段类型 | 备注 |
id | 主键 | 整型 | 自动生成 |
userid | 用户ID | 整型 | 外键,用户表主键 |
title | 投稿标题 | 字符型 |
|
time | 发布时间 | datatime型 |
|
zan | 用户点赞数 | 整型 | 默认为0 |
collection | 用户收藏数 | 整型 | 默认为0 |
picturename | 作品图片名称 | 字符型 | 包括文件后缀 |
fenleiid | 作品分类id | 整型 | 外键,分类表主键 |
评论表(Pinglun)
字段名 | 表示内容 | 字段类型 | 备注 |
id | 主键 | 整型 | 自动生成 |
userid | 用户ID | 整型 | 外键,用户表主键 |
tougaoid | 投稿ID | 整型 | 外键,投稿表主键 |
time | 评论时间 | datatime型 |
|
content | 评论内容 | 字符型 |
|
分类表(Fenlei)
字段名 | 表示内容 | 字段类型 | 备注 |
id | 主键 | 整型 | 自动生成 |
fenleiname | 分类名称 | 字符型 |
|
点赞表(Dianzan)
字段名 | 表示内容 | 字段类型 | 备注 |
id | 主键 | 整型 | 自动生成 |
userid | 用户ID | 整型 | 外键,用户表主键 |
tougaoid | 投稿ID | 整型 | 外键,用户表主键 |
收藏表(Soucang)
字段名 | 表示内容 | 字段类型 | 备注 |
id | 主键 | 整型 | 自动生成 |
userid | 用户ID | 整型 | 外键,用户表主键 |
tougaoid | 投稿ID | 整型 | 外键,用户表主键 |
五、系统实现的关键算法与数据结构
1、排序算法
这里的系统用了许多排序算法,通过查询时间、点赞数或收藏数来对投稿作品的展示进行排序。
@app.route('/')
def index():
context={
'touGao':Tougao.query.order_by('-time').all()
}
return render_template("index.html",**context)
2、加密算法
设置用户密码的时候分成内部使用和外部使用两种,内部使用的_password是管理员都看不见的,只能使用系统加密后的password进行使用,从而提高了用户的账户安全性。
class User(db.Model):
__tablename__='user'
id = db.Column(db.Integer,primary_key=True,autoincrement=True)
username=db.Column(db.String(20),nullable=False)
_password=db.Column(db.String(200),nullable=False)
userpic=db.Column(db.String(200),nullable=True,default="moren.jpg")
@property
def password(self):#外部使用
return self._password
@password.setter
def password(self,row_password):
self._password=generate_password_hash(row_password)
def check_password(self,row_password):
result=check_password_hash(self._password,row_password)
return result
六、成品展示
1、首页展示:
2、作品详情页展示:
3、热门图片展示:
4、分类导航下拉选项:
5、投稿页面:
6、个人主页:
7、修改头像页:
8、修改密码功能: