tornado web高级开发项目
抽屉官网:http://dig.chouti.com/
一、配置(settings)
settings = {
'template_path': 'views', #模板文件路径
'static_path': 'statics', #静态文件路径
'static_url_prefix': '/statics/', #静态文件前缀
'autoreload': True,
'ui_methods': mt
}
二、路由配置
application = tornado.web.Application([
(r"/index", home.IndexHandler), #主页
(r"/check_code", account.CheckCodeHandler), #验证码
(r"/send_msg", account.SendMsgHandler), #邮箱验证码
(r"/register", account.RegisterHandler), #注册
(r"/login", account.LoginHandler), #登陆
(r"/upload_image", home.UploadImageHandler), #上传图片
(r"/comment", home.CommentHandler), #评论
(r"/favor", home.FavorHandler), #点赞
], **settings)
三、文件夹分类
下面我们将根据上图文件目录由上到下做一一分析:
四、准备工作
本项目所有前端反馈均是通过BaseResponse类实现的:
class BaseResponse: def __init__(self):
self.status = False #状态信息,是否注册成功,是否登陆成功,是否点赞成功、是否评论成功等
self.code = StatusCodeEnum.Success
self.data = None #前端需要展示的数据
self.summary = None #错误信息
self.message = {} #字典类型的错误信息
前端:
- html
- css基础(一)
- css基础(二)
- JavaScript基础
- Dom基础和实例
- jquary基础和实例大全
- 偷偷发请求的ajax基础与实例大全
后端:
- web框架本质
- web框架之tronado
数据库:
- mysql基础一
- mysql基础二
- python操作mysql(pymysql 和ORM框架 SQLAchemy)
缓存:
RabbitMQ、Redis、Memcache、SQLAlchemy
五、core:业务处理类handler需要继承的父类
import tornado.web
from backend.session.session import SessionFactory class BaseRequestHandler(tornado.web.RequestHandler): def initialize(self): self.session = SessionFactory.get_session_obj(self)
六、form:用于form验证的文件,这是一个自定义的tornado form验证模块
fields:包含字符串、邮箱、数字、checkbox、文件类型验证
forms:核心验证处理,返回验证是否成功self._valid_status、成功后的数据提self._value_dict、错误信息self._error_dict
class Field: def __init__(self): self.is_valid = False
self.name = None
self.value = None
self.error = None def match(self, name, value):
self.name = name if not self.required:
self.is_valid = True
self.value = value
else:
if not value:
if self.custom_error_dict.get('required', None):
self.error = self.custom_error_dict['required']
else:
self.error = "%s is required" % name
else:
ret = re.match(self.REGULAR, value)
if ret:
self.is_valid = True
self.value = value
else:
if self.custom_error_dict.get('valid', None):
self.error = self.custom_error_dict['valid']
else:
self.error = "%s is invalid" % name
初始化
class StringField(Field): REGULAR = "^.*$" def __init__(self, custom_error_dict=None, required=True): self.custom_error_dict = {} # {'required': 'IP不能为空', 'valid': 'IP格式错误'}
if custom_error_dict:
self.custom_error_dict.update(custom_error_dict) self.required = required super(StringField, self).__init__()
字符串匹配
class IPField(Field): REGULAR = "^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$" def __init__(self, custom_error_dict=None, required=True): self.custom_error_dict = {} # {'required': 'IP不能为空', 'valid': 'IP格式错误'}
if custom_error_dict:
self.custom_error_dict.update(custom_error_dict) self.required = required
super(IPField, self).__init__()
ip匹配
class EmailField(Field): REGULAR = "^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$" def __init__(self, custom_error_dict=None, required=True): self.custom_error_dict = {} # {'required': 'IP不能为空', 'valid': 'IP格式错误'}
if custom_error_dict:
self.custom_error_dict.update(custom_error_dict) self.required = required
super(EmailField, self).__init__()
邮箱匹配
class IntegerField(Field): REGULAR = "^\d+$" def __init__(self, custom_error_dict=None, required=True): self.custom_error_dict = {} # {'required': 'IP不能为空', 'valid': 'IP格式错误'}
if custom_error_dict:
self.custom_error_dict.update(custom_error_dict) self.required = required
super(IntegerField, self).__init__()
数字匹配
class CheckBoxField(Field): REGULAR = "^\d+$" def __init__(self, custom_error_dict=None, required=True): self.custom_error_dict = {} # {'required': 'IP不能为空', 'valid': 'IP格式错误'}
if custom_error_dict:
self.custom_error_dict.update(custom_error_dict) self.required = required
super(CheckBoxField, self).__init__() def match(self, name, value):
self.name = name if not self.required:
self.is_valid = True
self.value = value
else:
if not value:
if self.custom_error_dict.get('required', None):
self.error = self.custom_error_dict['required']
else:
self.error = "%s is required" % name
else:
if isinstance(name, list):
self.is_valid = True
self.value = value
else:
if self.custom_error_dict.get('valid', None):
self.error = self.custom_error_dict['valid']
else:
self.error = "%s is invalid" % name
checkbox匹配
class FileField(Field): REGULAR = "^(\w+\.pdf)|(\w+\.mp3)|(\w+\.py)$" def __init__(self, custom_error_dict=None, required=True): self.custom_error_dict = {} # {'required': 'IP不能为空', 'valid': 'IP格式错误'}
if custom_error_dict:
self.custom_error_dict.update(custom_error_dict) self.required = required super(FileField, self).__init__() def match(self, name, file_name_list):
flag = True
self.name = name if not self.required:
self.is_valid = True
self.value = file_name_list
else:
if not file_name_list:
if self.custom_error_dict.get('required', None):
self.error = self.custom_error_dict['required']
else:
self.error = "%s is required" % name
flag = False
else:
for file_name in file_name_list:
if not file_name or not file_name.strip():
if self.custom_error_dict.get('required', None):
self.error = self.custom_error_dict['required']
else:
self.error = "%s is required" % name
flag = False
break
else:
ret = re.match(self.REGULAR, file_name)
if not ret:
if self.custom_error_dict.get('valid', None):
self.error = self.custom_error_dict['valid']
else:
self.error = "%s is invalid" % name
flag = False
break self.is_valid = flag def save(self, request, upload_to=""): file_metas = request.files[self.name]
for meta in file_metas:
file_name = meta['filename']
file_path_name = os.path.join(upload_to, file_name)
with open(file_path_name, 'wb') as up:
up.write(meta['body']) upload_file_path_list = map(lambda path: os.path.join(upload_to, path), self.value)
self.value = list(upload_file_path_list)
文件匹配
核心验证处理:
from backend.form import fields class BaseForm: def __init__(self):
self._value_dict = {} #数据字典
self._error_dict = {} #错误信息字典
self._valid_status = True #是否验证成功 def valid(self, handler): for field_name, field_obj in self.__dict__.items():
if field_name.startswith('_'): #过滤私有字段
continue if type(field_obj) == fields.CheckBoxField: #checkbox处理
post_value = handler.get_arguments(field_name, None)
elif type(field_obj) == fields.FileField: #文件处理
post_value = []
file_list = handler.request.files.get(field_name, [])
for file_item in file_list:
post_value.append(file_item['filename'])
else:
post_value = handler.get_argument(field_name, None) field_obj.match(field_name, post_value) #匹配
if field_obj.is_valid: #如果验证成功
self._value_dict[field_name] = field_obj.value #提取数据
else:
self._error_dict[field_name] = field_obj.error #错误信息
self._valid_status = False
return self._valid_status #返回是否验证成功
七、如何应用上述form验证模块:
以注册为例:
前端:
<div class="header">
<span>注册</span>
<div class="dialog-close" onclick="CloseDialog('#accountDialog');">X</div>
</div>
<div class="content">
<div style="padding: 0 70px">
<div class="tips">
<span>输入注册信息</span>
</div>
<div id="register_error_summary" class="error-msg"> </div>
<div class="inp">
<input name="username" type="text" placeholder="请输入用户名" /> </div>
<div class="inp">
<input name="email" id="email" type="text" placeholder="请输入邮箱" />
</div>
<div class="inp">
<input name="email_code" class="email-code" type="text" placeholder="请输入邮箱验证码" />
<a id="fetch_code" class="fetch-code" href="javascript:void(0);">获取验证码</a>
</div>
<div class="inp">
<input name="password" type="password" placeholder="请输入密码" />
</div>
<div class="inp">
<div class="submit" onclick="SubmitRegister(this);">
<span>注册</span>
<span class="hide">
<img src="/statics/images/loader.gif" style="height: 16px;width: 16px">
<span>正在注册</span>
</span>
</div>
</div>
</div>
</div>
Html
js:
/*
点击注册按钮
*/
function SubmitRegister(ths){
$('#register_error_summary').empty();
$('#model_register .inp .error').remove(); $(ths).children(':eq(0)').addClass('hide');
$(ths).addClass('not-allow').children(':eq(1)').removeClass('hide'); var post_dict = {};
$('#model_register input').each(function(){
post_dict[$(this).attr("name")] = $(this).val(); #将所有input标签内容提取出来,以对应name为key,值为value放入post_dict字典
}); $.ajax({
url: '/register', #提交的url
type: 'POST', #提交方式
data: post_dict, #提交数据
dataType: 'json', #数据格式
success: function(arg){
if(arg.status){
window.location.href = '/index'; #验证成功跳转至主页
}else{
$.each(arg.message, function(k,v){
//<span class="error">s</span>
var tag = document.createElement('span'); #验证失败创建标签
tag.className = 'error';
tag.innerText = v; #存入错误信息
$('#model_register input[name="'+ k +'"]').after(tag); #将标签加入html中
})
}
}
}); $(ths).removeClass('not-allow').children(':eq(1)').addClass('hide');
$(ths).children(':eq(0)').removeClass('hide');
}
js
后台处理:
首先需要编写RegisterForm:
class RegisterForm(BaseForm): #需要继承上面的form验证核心处理类 def __init__(self): #初始化每一个input标签的name
self.username = StringField() #input标签name=对应类型的类
self.email = EmailField()
self.password = StringField()
self.email_code = StringField() super(RegisterForm, self).__init__()
后台RegisterHandler:
class RegisterHandler(BaseRequestHandler):
def post(self, *args, **kwargs):
rep = BaseResponse() #总的返回前端的类,包含是否注册成功的状态、错误信息
form = account.RegisterForm() #实例化RegisterForm
if form.valid(self): #调用baseform核心验证处理函数valid,返回是否验证成功
current_date = datetime.datetime.now()
limit_day = current_date - datetime.timedelta(minutes=1)
conn = ORM.session() #获取数据库session对象<br> #查看验证码是否过期
is_valid_code = conn.query(ORM.SendMsg).filter(ORM.SendMsg.email == form._value_dict['email'],
ORM.SendMsg.code == form._value_dict['email_code'],
ORM.SendMsg.ctime > limit_day).count()
if not is_valid_code:
rep.message['email_code'] = '邮箱验证码不正确或过期'
self.write(json.dumps(rep.__dict__))
return
has_exists_email = conn.query(ORM.UserInfo).filter(ORM.UserInfo.email == form._value_dict['email']).count()#邮箱是否存在
if has_exists_email:
rep.message['email'] = '邮箱已经存在'
self.write(json.dumps(rep.__dict__))
return
has_exists_username = conn.query(ORM.UserInfo).filter(
ORM.UserInfo.username == form._value_dict['username']).count() #用户名是否存在
if has_exists_username:
rep.message['email'] = '用户名已经存在'
self.write(json.dumps(rep.__dict__))
return<br> #按数据库表的列订制form._value_dict
form._value_dict['ctime'] = current_date form._value_dict.pop('email_code') obj = ORM.UserInfo(**form._value_dict) conn.add(obj)<br>
conn.flush()
conn.refresh(obj) #将自增id也提取出来 user_info_dict = {'nid': obj.nid, 'email': obj.email, 'username': obj.username} conn.query(ORM.SendMsg).filter_by(email=form._value_dict['email']).delete()#删除本次邮箱验证码
conn.commit()
conn.close() self.session['is_login'] = True #注册成功后定义登陆成功
self.session['user_info'] = user_info_dict 用户信息写入session
rep.status = True else:
rep.message = form._error_dict #错误信息 self.write(json.dumps(rep.__dict__)) #返回给前端,前端ajax success接收并处理,在前端页面展示
八、session ,本session是基于tornado的自定义session
1.应用工厂方法模式定义session保存的位置,用户只需在配置文件修改即可
class SessionFactory: @staticmethod
def get_session_obj(handler):
obj = None if config.SESSION_TYPE == "cache": #缓存
obj = CacheSession(handler)
elif config.SESSION_TYPE == "memcached": #memcached
obj = MemcachedSession(handler) <br> elif config.SESSION_TYPE == "redis": #radis<br> obj = RedisSession(handler) <br> return obj
2.缓存session
class CacheSession:
session_container = {}
session_id = "__sessionId__" def __init__(self, handler):
self.handler = handler
client_random_str = handler.get_cookie(CacheSession.session_id, None)
if client_random_str and client_random_str in CacheSession.session_container:
self.random_str = client_random_str
else:
self.random_str = create_session_id()
CacheSession.session_container[self.random_str] = {} expires_time = time.time() + config.SESSION_EXPIRES
handler.set_cookie(CacheSession.session_id, self.random_str, expires=expires_time) def __getitem__(self, key):
ret = CacheSession.session_container[self.random_str].get(key, None)
return ret def __setitem__(self, key, value):
CacheSession.session_container[self.random_str][key] = value def __delitem__(self, key):
if key in CacheSession.session_container[self.random_str]:
del CacheSession.session_container[self.random_str][key]
缓存Sesson
3.memcache session
import memcache conn = memcache.Client(['192.168.11.119:12000'], debug=True, cache_cas=True) class MemcachedSession:
session_id = "__sessionId__" def __init__(self, handler):
self.handler = handler
# 从客户端获取随机字符串
client_random_str = handler.get_cookie(CacheSession.session_id, None)
# 如果从客户端获取到了随机字符串
#
if client_random_str and conn.get(client_random_str):
self.random_str = client_random_str
else:
self.random_str = create_session_id()
conn.set(self.random_str, json.dumps({}), config.SESSION_EXPIRES)
#CacheSession.session_container[self.random_str] = {} conn.set(self.random_str, conn.get(self.random_str), config.SESSION_EXPIRES) expires_time = time.time() + config.SESSION_EXPIRES
handler.set_cookie(MemcachedSession.session_id, self.random_str, expires=expires_time) def __getitem__(self, key):
# ret = CacheSession.session_container[self.random_str].get(key, None)
ret = conn.get(self.random_str)
ret_dict = json.loads(ret)
result = ret_dict.get(key,None)
return result def __setitem__(self, key, value):
ret = conn.get(self.random_str)
ret_dict = json.loads(ret)
ret_dict[key] = value
conn.set(self.random_str, json.dumps(ret_dict), config.SESSION_EXPIRES) # CacheSession.session_container[self.random_str][key] = value def __delitem__(self, key):
ret = conn.get(self.random_str)
ret_dict = json.loads(ret)
del ret_dict[key]
conn.set(self.random_str, json.dumps(ret_dict), config.SESSION_EXPIRES)
memcache session
4.radis session
import redis pool = redis.ConnectionPool(host='192.168.11.119', port=6379)
r = redis.Redis(connection_pool=pool) class RedisSession:
session_id = "__sessionId__" def __init__(self, handler):
self.handler = handler
# 从客户端获取随机字符串
client_random_str = handler.get_cookie(CacheSession.session_id, None)
# 如果从客户端获取到了随机字符串
if client_random_str and r.exists(client_random_str):
self.random_str = client_random_str
else:
self.random_str = create_session_id()
r.hset(self.random_str,None,None) # conn.set(self.random_str, json.dumps({}), config.SESSION_EXPIRES)
# CacheSession.session_container[self.random_str] = {}
r.expire(self.random_str, config.SESSION_EXPIRES)
# conn.set(self.random_str, conn.get(self.random_str), config.SESSION_EXPIRES) expires_time = time.time() + config.SESSION_EXPIRES
handler.set_cookie(RedisSession.session_id, self.random_str, expires=expires_time) def __getitem__(self, key):
# ret = CacheSession.session_container[self.random_str].get(key, None)
result = r.hget(self.random_str,key)
if result:
ret_str = str(result, encoding='utf-8')
try:
result = json.loads(ret_str)
except:
result = ret_str
return result
else:
return result def __setitem__(self, key, value):
if type(value) == dict:
r.hset(self.random_str, key, json.dumps(value))
else:
r.hset(self.random_str, key, value) # CacheSession.session_container[self.random_str][key] = value def __delitem__(self, key):
r.hdel(self.random_str,key)
radis session
九、验证码:
注:验证码需要依赖session。
#!/usr/bin/env python
#coding:utf-8 import random
from PIL import Image, ImageDraw, ImageFont, ImageFilter _letter_cases = "abcdefghjkmnpqrstuvwxy" # 小写字母,去除可能干扰的i,l,o,z
_upper_cases = _letter_cases.upper() # 大写字母
_numbers = ''.join(map(str, range(3, 10))) # 数字
init_chars = ''.join((_letter_cases, _upper_cases, _numbers)) def create_validate_code(size=(120, 30),
chars=init_chars,
img_type="GIF",
mode="RGB",
bg_color=(255, 255, 255),
fg_color=(0, 0, 255),
font_size=18,
font_type="Monaco.ttf",
length=4,
draw_lines=True,
n_line=(1, 2),
draw_points=True,
point_chance = 2):
'''
@todo: 生成验证码图片
@param size: 图片的大小,格式(宽,高),默认为(120, 30)
@param chars: 允许的字符集合,格式字符串
@param img_type: 图片保存的格式,默认为GIF,可选的为GIF,JPEG,TIFF,PNG
@param mode: 图片模式,默认为RGB
@param bg_color: 背景颜色,默认为白色
@param fg_color: 前景色,验证码字符颜色,默认为蓝色#0000FF
@param font_size: 验证码字体大小
@param font_type: 验证码字体,默认为 ae_AlArabiya.ttf
@param length: 验证码字符个数
@param draw_lines: 是否划干扰线
@param n_lines: 干扰线的条数范围,格式元组,默认为(1, 2),只有draw_lines为True时有效
@param draw_points: 是否画干扰点
@param point_chance: 干扰点出现的概率,大小范围[0, 100]
@return: [0]: PIL Image实例
@return: [1]: 验证码图片中的字符串
''' width, height = size # 宽, 高
img = Image.new(mode, size, bg_color) # 创建图形
draw = ImageDraw.Draw(img) # 创建画笔 def get_chars():
'''生成给定长度的字符串,返回列表格式'''
return random.sample(chars, length) def create_lines():
'''绘制干扰线'''
line_num = random.randint(*n_line) # 干扰线条数 for i in range(line_num):
# 起始点
begin = (random.randint(0, size[0]), random.randint(0, size[1]))
#结束点
end = (random.randint(0, size[0]), random.randint(0, size[1]))
draw.line([begin, end], fill=(0, 0, 0)) def create_points():
'''绘制干扰点'''
chance = min(100, max(0, int(point_chance))) # 大小限制在[0, 100] for w in range(width):
for h in range(height):
tmp = random.randint(0, 100)
if tmp > 100 - chance:
draw.point((w, h), fill=(0, 0, 0)) def create_strs():
'''绘制验证码字符'''
c_chars = get_chars()
strs = ' %s ' % ' '.join(c_chars) # 每个字符前后以空格隔开 font = ImageFont.truetype(font_type, font_size)
font_width, font_height = font.getsize(strs) draw.text(((width - font_width) / 3, (height - font_height) / 3),
strs, font=font, fill=fg_color) return ''.join(c_chars) if draw_lines:
create_lines()
if draw_points:
create_points()
strs = create_strs() # 图形扭曲参数
params = [1 - float(random.randint(1, 2)) / 100,
0,
0,
0,
- float(random.randint(1, 10)) / 100,
float(random.randint(1, 2)) / 500,
0.001,
float(random.randint(1, 2)) / 500
]
img = img.transform(size, Image.PERSPECTIVE, params) # 创建扭曲 img = img.filter(ImageFilter.EDGE_ENHANCE_MORE) # 滤镜,边界加强(阈值更大) return img, strs
check_code.py
class CheckCodeHandler(BaseRequestHandler):
def get(self, *args, **kwargs):
stream = io.BytesIO()
img, code = check_code.create_validate_code()
img.save(stream, "png")
self.session["CheckCode"] = code #利用session保存验证码
self.write(stream.getvalue())
路由配置:(r"/check_code", account.CheckCodeHandler),
前端:
<img class="check-img" src="/check_code" alt="验证码" onclick="ChangeCode(this);">
js:
<script>
function ChangeCode(ths) {
ths.src += '?';
}
</script>
十、发送邮箱验证码
前端:
<div class="inp">
<input class="regiter-temp" name="code" class="email-code" type="text" placeholder="请输入邮箱验证码" />
<a onclick="SendCode(this);" class="fetch-code" >获取验证码</a>
</div>
js:
function SendCode(ths) {
// var email = $(ths).prev().val();
var email = $('#email').val();
$.ajax({
url: '/send_code',
type: 'POST',
data: {em: email},
success: function (arg) {
console.log(arg);
},
error: function () { } });
}
路由配置:
(r"/send_code", account.SendCodeHandler),
后台handler:
class SendCodeHandler(BaseRequestHandler):
def post(self, *args, **kwargs):
ret = {'status': True, "data": "", "error": ""}
email = self.get_argument('em', None)
if email:
code = commons.random_code() #获取随机验证码
message.email([email,], code) #发送验证码到邮箱
conn = chouti_orm.session() #获取数据库session对象
obj = chouti_orm.SendCode(email=email,code=code, stime=datetime.datetime.now()) #写入数据库
conn.add(obj)
conn.commit()
else:
ret['status'] = False
ret['error'] = "邮箱格式错误" self.write(json.dumps(ret))
发送邮箱验证码函数:
import smtplib
from email.mime.text import MIMEText
from email.utils import formataddr def email(email_list, content, subject="抽屉新热榜-用户注册"): #email_list邮件列表,content邮件内容,subject:发送标题
msg = MIMEText(content, 'plain', 'utf-8')
msg['From'] = formataddr(["抽屉新热榜",'wptawy@126.com'])
msg['Subject'] = subject server = smtplib.SMTP("smtp.126.com", 25) 邮箱引擎
server.login("youxiang@126.com", "mima") #邮箱名,密码
server.sendmail('wptawy@126.com', email_list, msg.as_string())
server.quit()
十一、邮箱验证码之过期时间
案例:
html:
<a id="fetch_code" class="fetch-code" href="javascript:void(0);">获取验证码</a>
js:
function BindSendMsg(){
$("#fetch_code").click(function(){
$('#register_error_summary').empty(); #清空错误信息
var email = $('#email').val(); #获取邮箱地址
if(email.trim().length == 0){ #判断是否输入邮箱
$('#register_error_summary').text('请输入注册邮箱');
return;
}
if($(this).hasClass('sending')){ #判断是否已经发送
return;
}
var ths = $(this);
var time = 60; 设置倒计时时间为60s $.ajax({
url: "/send_msg",
type: 'POST',
data: {email: email},
dataType: 'json',
success: function(arg){
if(!arg.status){ #是否发送成功
$('#register_error_summary').text(arg.summary); #不成功显示错误信息
}else{
ths.addClass('sending'); #成功后显示已发送状态
var interval = setInterval(function(){
ths.text("已发送(" + time + ")");
time -= 1; #定时器每运行一次,计数器减1
if(time <= 0){
clearInterval(interval); #一分钟过完,清除定时器
ths.removeClass('sending');# 移除已发送状态
ths.text("获取验证码");# 恢复未发送状态
}
}, 1000);#定时器每隔1s运行一次
}
}
}); });
}
附:一些常见模块:
1.随机验证码获取:
def random_code():
code = ''
for i in range(4):
current = random.randrange(0,4)
if current != i:
temp = chr(random.randint(65,90))
else:
temp = random.randint(0,9)
code += str(temp)
return code
随机验证码
2.md5加密
def generate_md5(value):
r = str(time.time())
obj = hashlib.md5(r.encode('utf-8'))
obj.update(value.encode('utf-8'))
return obj.hexdigest()
十二、分页功能,该功能是基于tornado的自定义分页功能
案例:
前端:
<div class="pagination">
{% raw str_page%} #展示原生html
</div>
url配置:
(r"/index/(?P<page>\d*)", IndexHandler),
分页模块:
#!/usr/bin/env python
# -*- coding:utf-8 -*- class Pagination:
def __init__(self, current_page, all_item):
try:
page = int(current_page)
except:
page = 1
if page < 1:
page = 1 all_pager, c = divmod(all_item, 10)
if c > 0:
all_pager += 1 self.current_page = page
self.all_pager = all_pager @property
def start(self):
return (self.current_page - 1) * 10 @property
def end(self):
return self.current_page * 10 def string_pager(self, base_url="/index/"):
list_page = []
if self.all_pager < 11:
s = 1
t = self.all_pager + 1
else: # 总页数大于11
if self.current_page < 6:
s = 1
t = 12
else:
if (self.current_page + 5) < self.all_pager:
s = self.current_page - 5
t = self.current_page + 5 + 1
else:
s = self.all_pager - 11
t = self.all_pager + 1
# 首页
# first = '<a href="%s1">首页</a>' % base_url
# list_page.append(first)
# 上一页
# 当前页 page
if self.current_page == 1:
prev = '<a href="javascript:void(0);">上一页</a>'
else:
prev = '<a href="%s%s">上一页</a>' % (base_url, self.current_page - 1,)
list_page.append(prev) for p in range(s, t): # 1-11
if p == self.current_page:
temp = '<a class="active" href="%s%s">%s</a>' % (base_url,p, p)
else:
temp = '<a href="%s%s">%s</a>' % (base_url,p, p)
list_page.append(temp)
if self.current_page == self.all_pager:
nex = '<a href="javascript:void(0);">下一页</a>'
else:
nex = '<a href="%s%s">下一页</a>' % (base_url, self.current_page + 1,) list_page.append(nex) # 尾页
last = '<a href="%s%s">尾页</a>' % (base_url, self.all_pager,)
list_page.append(last) # 跳转
jump = """<input type='text' /><a onclick="Jump('%s',this);">GO</a>""" % ('/index/', )
script = """<script>
function Jump(baseUrl,ths){
var val = ths.previousElementSibling.value;
if(val.trim().length>0){
location.href = baseUrl + val;
}
}
</script>"""
list_page.append(jump)
list_page.append(script)
str_page = "".join(list_page)
return str_page
分页模块
注:Pagination实例化接收两个参数:当前页current_page、新闻总数all_item,其中current_page一般通过url分组元素直接获取
后台handler:
class IndexHandler(BaseRequestHandler):
def get(self, page=1):
conn = ORM.session() #获取数据库session对象
all_count = conn.query(ORM.News).count()#计算新闻总数
obj = Pagination(page, all_count) #实例化pagination对象
current_user_id = self.session['user_info']['nid'] if self.session['is_login'] else 0 #如果登陆获取用户id,否则,用户id=0,下面的查询结果也为空
result = conn.query(ORM.News.nid,
ORM.News.title,
ORM.News.url,
ORM.News.content,
ORM.News.ctime,
ORM.UserInfo.username,
ORM.NewsType.caption,
ORM.News.favor_count,
ORM.News.comment_count,
ORM.Favor.nid.label('has_favor')).join(ORM.NewsType, isouter=True).join(ORM.UserInfo, isouter=True).join(ORM.Favor, and_(ORM.Favor.user_info_id == current_user_id, ORM.News.nid == ORM.Favor.news_id), isouter=True)[obj.start:10] #从每页开始向下取10条,即每页显示10条新闻
conn.close()
str_page = obj.string_pager('/index/') 获取页码的字符串格式html
self.render('home/index.html', str_page=str_page, news_list=result)
十三、页面登陆验证(装饰器方式实现)
1.普通登陆验证
def auth_login_redirect(func): def inner(self, *args, **kwargs):
if not self.session['is_login']:
self.redirect(config.LOGIN_URL)
return
func(self, *args, **kwargs)
return inner
2.ajax提交数据的登陆验证
def auth_login_json(func): def inner(self, *args, **kwargs):
if not self.session['is_login']:
rep = BaseResponse()
rep.summary = "auth failed"
self.write(json.dumps(rep.__dict__))
return
func(self, *args, **kwargs)
return inner
十四、上传文件
前端:
<form style="display: inline-block" id="upload_img_form" name="form" action="/upload_image" method="POST" enctype="multipart/form-data" >
<a id="fakeFile" class="fake-file">
<span>上传图片</span>
<input type="file" name="img" onchange="UploadImage(this);"/>
<input type="text" name="url" class="hide" />
</a>
<iframe id='upload_img_iframe' name='upload_img_iframe' src="" class="hide"></iframe>
</form>
js:
function UploadImage(ths){
document.getElementById('upload_img_iframe').onload = UploadImageComplete; #页面加载完成后执行UploadImageComplete函数
document.getElementById('upload_img_form').target = 'upload_img_iframe'; 设置form提交到iframe
document.getElementById('upload_img_form').submit(); #提交到iframe
} /*
上传图片之后回掉函数
*/
function UploadImageComplete(){
var origin = $("#upload_img_iframe").contents().find("body").text();#获取图片数据
var obj = JSON.parse(origin); #转换成JavaScript对象
if(obj.status){ #如果上传成功
var img = document.createElement('img'); #创建img标签
img.src = obj.data; 图片地址
img.style.width = "200px";
img.style.height = "180px";
$("#upload_img_form").append(img);添加图片
$('#fakeFile').addClass('hide');
$('#reUploadImage').removeClass('hide');
$('#fakeFile').find('input[type="text"]').val(obj.data);#保存图片地址到隐藏的input标签中
}else{
alert(obj.summary);#否则显示错误信息
}
}
js
路由配置:
(r"/upload_image", home.UploadImageHandler),
后台handler:
class UploadImageHandler(BaseRequestHandler):
@decrator.auth_login_json #上传前登陆验证
def post(self, *args, **kwargs):
rep = BaseResponse() 前端回应类
try:
file_metas = self.request.files["img"] 获取图片列表
for meta in file_metas:
file_name = meta['filename'] #图片名
file_path = os.path.join('statics', 'upload', commons.generate_md5(file_name)) #保存地址
with open(file_path, 'wb') as up:
up.write(meta['body']) #在服务器写入图片
rep.status = True #写入成功
rep.data = file_path
except Exception as ex:
rep.summary = str(ex)#错误信息
self.write(json.dumps(rep.__dict__)) #反馈给前端
十五、文章发布
1.定义需要验证的form类
class IndexForm(BaseForm): def __init__(self):
self.title = StringField() #标题
self.content = StringField(required=False) 内容
self.url = StringField(required=False) 图片url
self.news_type_id = IntegerField() 新闻类型 super(IndexForm, self).__init__()
2.前端html:
<div class="f4">
<a class="submit right" id="submit_img">提交</a>
<span class="error-msg right"></span>
</div>
3.js
function BindPublishSubmit(){
$('#submit_link,#submit_text,#submit_img').click(function(){
// 获取输入内容并提交
var container = $(this).parent().parent();
var post_dict = {};
container.find('input[type="text"],textarea').each(function(){
post_dict[$(this).attr('name')] =$(this).val();
});
post_dict['news_type_id'] = container.find('.news-type .active').attr('value'); $.ajax({
url: '/index',
type: 'POST',
data: post_dict,
dataType: 'json',
success: function (arg) {
if(arg.status){
window.location.href = '/index';
}else{
console.log(arg);
}
}
})
});
}
后台handler:
@decrator.auth_login_json #发布前登陆验证
def post(self, *args, **kwargs):
rep = BaseResponse() form = IndexForm()#实例化Indexform
if form.valid(self):
# title,content,href,news_type,user_info_id
#写入数据库
input_dict = copy.deepcopy(form._value_dict)
input_dict['ctime'] = datetime.datetime.now()
input_dict['user_info_id'] = self.session['user_info']['nid']
conn = ORM.session()
conn.add(ORM.News(**input_dict))
conn.commit()
conn.close()
rep.status = True #写入成功
else:
rep.message = form._error_dict #错误信息 self.write(json.dumps(rep.__dict__))
十六、点赞功能
前端html:
<a href="javascript:void(0);" class="digg-a" title="推荐" onclick="DoFavor(this,{{item[0]}});">
{% if item[9] %} #是否已点过赞
<span class="hand-icon icon-digg active"></span>
{% else %}
<span class="hand-icon icon-digg"></span>
{% end %}
<b id="favor_count_{{item[0]}}">{{item[7]}}</b> #点赞数量<br></a>
js:
function DoFavor(ths, nid) { if($('#action_nav').attr('is-login') == 'true'){ #登陆状态才能点赞
$.ajax({
url: '/favor',
type: 'POST',
data: {news_id: nid}, #携带当前新闻id
dataType: 'json',
success: function(arg){
if(arg.status){
var $favorCount = $('#favor_count_'+nid);
var c = parseInt($favorCount.text());#获取当前点赞数量
if(arg.code == 2301){ #当前用户以前没点过赞
$favorCount.text(c + 1); #点赞数量加1
$(ths).find('span').addClass('active'); 已经点过赞变深颜色
AddFavorAnimation(ths); #+1动态效果
}else if(arg.code == 2302){ #该用户以前对该新闻点过赞
$favorCount.text(c - 1); #点赞数量减1
$(ths).find('span').removeClass('active'); 取消点赞颜色变浅
MinusFavorAnimation(ths); #-1动态效果
}else{ } }else{ }
}
})
}else{
$('#accountDialog').removeClass('hide');
$('.shadow').removeClass('hide');
}
}
点赞
后台handler:
class FavorHandler(BaseRequestHandler): @decrator.auth_login_json #点赞前登陆验证
def post(self, *args, **kwargs):
rep = BaseResponse() news_id = self.get_argument('news_id', None) #获取新闻id
if not news_id:
rep.summary = "新闻ID不能为空."
else:
user_info_id = self.session['user_info']['nid'] #获取当前用户id
conn = ORM.session()<br> #查询当前用户是否对该新闻点过赞
has_favor = conn.query(ORM.Favor).filter(ORM.Favor.user_info_id == user_info_id,
ORM.Favor.news_id == news_id).count()
if has_favor: #如果已经点过赞,删除数据库点赞表favor点赞数据,更新数据库新闻表news点赞数量-1
conn.query(ORM.Favor).filter(ORM.Favor.user_info_id == user_info_id,
ORM.Favor.news_id == news_id).delete()
conn.query(ORM.News).filter(ORM.News.nid == news_id).update(
{"favor_count": ORM.News.favor_count - 1}, synchronize_session="evaluate")
rep.code = StatusCodeEnum.FavorMinus #返回已经点过赞的状态吗
else:
conn.add(ORM.Favor(user_info_id=user_info_id, news_id=news_id, ctime=datetime.datetime.now()))
conn.query(ORM.News).filter(ORM.News.nid == news_id).update(
{"favor_count": ORM.News.favor_count + 1}, synchronize_session="evaluate")
rep.code = StatusCodeEnum.FavorPlus
conn.commit()
conn.close() rep.status = True #操作成功 self.write(json.dumps(rep.__dict__)) #返回给前端
点赞+1和-1动态效果js:
/*
点赞+1效果
*/
function AddFavorAnimation(ths){
var offsetTop = -10;
var offsetLeft = 20;
var fontSize = 24;
var opacity = 1;
var tag = document.createElement('i');
tag.innerText = "+1";
tag.style.position = 'absolute';
tag.style.top = offsetTop + 'px';
tag.style.left = offsetLeft + 'px';
tag.style.fontSize = fontSize + "px";
tag.style.color = "#5cb85c";
$(ths).append(tag); var addInterval = setInterval(function(){
fontSize += 5;
offsetTop -= 15;
offsetLeft += 5;
opacity -= 0.1;
tag.style.top = offsetTop+ 'px';
tag.style.left = offsetLeft+ 'px';
tag.style.fontSize = fontSize + 'px';
tag.style.opacity = opacity;
if(opacity <= 0.5){
tag.remove();
clearInterval(addInterval);
}
},40)
}
点赞+1效果
/*
点赞-1效果
*/
function MinusFavorAnimation(ths){
var offsetTop = -10;
var offsetLeft = 20;
var fontSize = 24;
var opacity = 1;
var tag = document.createElement('i');
tag.innerText = "-1";
tag.style.position = 'absolute';
tag.style.top = offsetTop + 'px';
tag.style.left = offsetLeft + 'px';
tag.style.fontSize = fontSize + "px";
tag.style.color = "#787878";
$(ths).append(tag); var addInterval = setInterval(function(){
fontSize += 5;
offsetTop -= 15;
offsetLeft += 5 ;
opacity -= 0.1;
tag.style.top = offsetTop+ 'px';
tag.style.left = offsetLeft+ 'px';
tag.style.fontSize = fontSize + 'px';
tag.style.opacity = opacity;
if(opacity <= 0.5){
tag.remove();
clearInterval(addInterval);
}
},40)
}
点赞-1效果
十七、评论功能
案例:
前端html:
<div class="box-r">
<a href="javascript:void(0);" class="pub-icons add-pub-btn add-pub-btn-unvalid" onclick="DoComment({{item[0]}})">评论</a> #携带新闻id
<a href="javascript:void(0);" class="loading-ico loading-ico-top pub-loading-top hide">发布中...</a>
</div>
{% raw tree(comment_tree) %}
创建评论树字典函数:
def build_tree(comment_list): comment_dic = collections.OrderedDict() for comment_obj in comment_list:
if comment_obj[2] is None:
# 如果是根评论,添加到comment_dic[评论对象] = {}
comment_dic[comment_obj] = collections.OrderedDict()
else:
# 如果是回复的评论,则需要在 comment_dic 中找到其回复的评论
tree_search(comment_dic, comment_obj)
return comment_dic
build_tree
递归生成评论树函数:
def tree_search(d_dic, comment_obj):
# 在comment_dic中一个一个的寻找其回复的评论
# 检查当前评论的 reply_id 和 comment_dic中已有评论的nid是否相同,
# 如果相同,表示就是回复的此信息
# 如果不同,则需要去 comment_dic 的所有子元素中寻找,一直找,如果一系列中未找,则继续向下找
for k, v_dic in d_dic.items():
# 找回复的评论,将自己添加到其对应的字典中,例如: {评论一: {回复一:{},回复二:{}}}
if k[0] == comment_obj[2]:
d_dic[k][comment_obj] = collections.OrderedDict()
return
else:
# 在当前第一个跟元素中递归的去寻找父亲
tree_search(d_dic[k], comment_obj)
tree_search
前端调用uimethod,生成评论html:
TEMP1 = """
<li class="items" style='padding:8px 0 0 %spx;'>
<span class="folder" id='comment_folder_%s'>
<div class="comment-L comment-L-top">
<a href="#" class="icons zhan-ico"></a>
<a href="/user/moyujian/submitted/1">
<img src="/statics/images/1.jpg">
</a>
</div>
<div class="comment-R comment-R-top" style="background-color: rgb(246, 246, 246);">
<div class="pp">
<a class="name" href="/user/moyujian/submitted/1">%s</a>
<span class="p3">%s</span>
<span class="into-time into-time-top">%s</span>
</div>
<div class="comment-line-top">
<div class="comment-state">
<a class="ding" href="javascript:void(0);">
<b>顶</b>
<span class="ding-num">[0]</span>
</a>
<a class="cai" href="javascript:void(0);">
<b>踩</b>
<span class="cai-num">[0]</span>
</a>
<span class="line-huifu">|</span>
<a class="see-a jubao" href="javascript:void(0);">举报</a>
<span class="line-huifu">|</span>
<a class="see-a huifu-a" href="javascript:void(0);" onclick="reply(%s,%s,'%s')" id='comment_reply_%s' >回复</a>
</div>
</div>
</div>
</span> """
def tree(self, comment_dic):
html = ''
for k, v in comment_dic.items():
html += TEMP1 %(0,k[0], k[3],k[1],k[4],k[7],k[0], k[3],k[0])
html += generate_comment_html(v, 16)
html += "</li>" return html def generate_comment_html(sub_comment_dic, margin_left_val):
# html = '<ul style="background: url("/statics/images/pinglun_line.gif") 0px -10px no-repeat scroll transparent;margin-left:3px;">'
html = '<ul>'
for k, v_dic in sub_comment_dic.items():
html += TEMP1 %(margin_left_val,k[0], k[3],k[1],k[4],k[7],k[0], k[3],k[0])
if v_dic:
html += generate_comment_html(v_dic, margin_left_val)
html += "</li>"
html += "</ul>"
return html
uimethod
后台handler:
class CommentHandler(BaseRequestHandler):
def get(self, *args, **kwargs): #展示评论信息
# comment_list需要按照时间从小到大排列
nid = self.get_argument('nid', 0)
conn = ORM.session()
comment_list = conn.query(
ORM.Comment.nid,
ORM.Comment.content,
ORM.Comment.reply_id,
ORM.UserInfo.username,
ORM.Comment.ctime,
ORM.Comment.up,
ORM.Comment.down,
ORM.Comment.news_id
).join(ORM.UserInfo, isouter=True).filter(ORM.Comment.news_id == nid).all() conn.close()
"""
comment_list = [
(1, '111',None), #评论id,评论内容,回复id,如果是None,则代表回复新闻
(2, '222',None),
(3, '33',None),
(9, '999',5),
(4, '444',2),
(5, '555',1),
(6, '666',4),
(7, '777',2),
(8, '888',4),
]
""" comment_tree = commons.build_tree(comment_list) #最终传递给前端的是一个字典 self.render('include/comment.html', comment_tree=comment_tree) @decrator.auth_login_json #提交评论前做登陆验证
def post(self, *args, **kwargs): #提交评论信息
rep = BaseResponse() #前端反馈类
form = CommentForm() #评论的form验证 if form.valid(self): #如果验证成功
form._value_dict['ctime'] = datetime.datetime.now() #获取当前时间 conn = ORM.session() #获取数据库session对象
# 将评论写入数据库
obj = ORM.Comment(user_info_id=self.session['user_info']['nid'],
news_id=form._value_dict['news_id'],
reply_id=form._value_dict['reply_id'],
content=form._value_dict['content'],
up=0,
down=0,
ctime=datetime.datetime.now())
conn.add(obj)
conn.flush()
conn.refresh(obj) #同时获取评论的自增id rep.data = {
'user_info_id': self.session['user_info']['nid'],
'username': self.session['user_info']['username'],
'nid': obj.nid,
'news_id': obj.news_id,
'ctime': obj.ctime.strftime("%Y-%m-%d %H:%M:%S"),
'reply_id': obj.reply_id,
'content': obj.content,
}
#更新数据库的评论数量
conn.query(ORM.News).filter(ORM.News.nid == form._value_dict['news_id']).update(
{"comment_count": ORM.News.comment_count + 1}, synchronize_session="evaluate")
conn.commit()
conn.close() rep.status = True #评论成功的状态信息
else:
rep.message = form._error_dict #错误信息
print(rep.__dict__)
self.write(json.dumps(rep.__dict__)) #反馈给前端
后台handler
js:
function DoComment(nid){
var content = $("#comment_content_"+nid).val();
var reply_id = $("#reply_id_"+nid).attr('target'); if($('#action_nav').attr('is-login') == 'true'){ #已经登录才发ajax请求
$.ajax({
url: '/comment',
type: 'POST',
data: {content: content, reply_id:reply_id, news_id: nid},#发送评论内容,回复的评论id,新闻id
dataType: 'json',
success: function(arg){
// 获取评论信息,将内容添加到指定位置
console.log(arg);
if(arg.status){
$('#comment_content_' + arg.data.news_id).val('');
var a = '<ul><li class="items" style="padding:8px 0 0 16px;"><span class="folder" id="comment_folder_';
var b = arg.data.nid;
var c = '"><div class="comment-L comment-L-top"><a href="#" class="icons zhan-ico"></a><a href="/user/moyujian/submitted/1"><img src="/statics/images/1.jpg"></a></div><div class="comment-R comment-R-top" style=""><div class="pp"><a class="name" href="/user/moyujian/submitted/1">';
var d = arg.data.username;
var e = '</a><span class="p3">';
var f = arg.data.content;
var g= '</span><span class="into-time into-time-top">';
var h = arg.data.ctime;
var i = '</span></div><div class="comment-line-top"><div class="comment-state"><a class="ding" href="javascript:void(0);"><b>顶</b><span class="ding-num">[0]</span></a><a class="cai" href="javascript:void(0);"><b>踩</b><span class="cai-num">[0]</span></a><span class="line-huifu">|</span> <a class="see-a jubao" href="javascript:void(0);">举报</a> <span class="line-huifu">|</span> <a class="see-a huifu-a" href="javascript:void(0);" onclick="';
var j = "reply(" + arg.data.news_id + "," +arg.data.nid+",'"+arg.data.username+"')";
var k = '">回复</a></div></div></div></span></li></ul>';
var tag = a+b+c+d+e+f+g+h+i+j+k;
console.log(arg,tag);
if(arg.data.reply_id){
$comment_folder = $('#comment_folder_' + arg.data.reply_id);
$comment_folder.after(tag);
}else{
$('#comment_list_'+arg.data.news_id).append(tag);
} }else{
alert('error');
}
}
})
}else{
$('#accountDialog').removeClass('hide');
$('.shadow').removeClass('hide');
}
}
js
tornado web高级开发项目的更多相关文章
- tornado web高级开发项目之抽屉官网的页面登陆验证、form验证、点赞、评论、文章分页处理、发送邮箱验证码、登陆验证码、注册、发布文章、上传图片
本博文将一步步带领你实现抽屉官网的各种功能:包括登陆.注册.发送邮箱验证码.登陆验证码.页面登陆验证.发布文章.上传图片.form验证.点赞.评论.文章分页处理以及基于tornado的后端和ajax的 ...
- (数据科学学习手札121)Python+Dash快速web应用开发——项目结构篇
本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 这是我的系列教程Python+Dash快速web ...
- [转]web高级开发的成长之路
读了这篇文章之后感觉蛮受启发的,在此分享一下,献给和我一样处于困惑的朋友. 正文如下: 本人也是coding很多年,虽然很失败,但也总算有点失败的心得,不过我在中国,大多数程序员都是像我一样,在一直走 ...
- WEB前端开发流程总结
作者声明:本博客中所写的文章,都是博主自学过程的笔记,参考了很多的学习资料,学习资料和笔记会注明出处,所有的内容都以交流学习为主.有不正确的地方,欢迎批评指正 WEB前端开发项目流程总结 1.新建项目 ...
- 上海洋码头(www.ymatou.com)急招技术人才(职位:互联网软件开发工程师,.NET网站架构师,Web前端开发工程师,高级测试工程师,产品经理)
对公司招聘职位有兴趣的童鞋可以把简历发送到zhangzhiqiang@ymatou.com,我们HR会快速给你答复. 互联网软件开发工程师 岗位职责: 1.参与洋码头各个平台(www.ymatou.c ...
- web前端学习路线(含20个真实web开发项目集合)
目前web前端工程师日均岗位缺口已经超过50000,随着互联网+的深入发展,html5作为前端展示技术,市场人才需求量将呈直线上涨. Web前端工程师的岗位职责是利用HTML.CSS.Java.DOM ...
- 招聘:web前端开发(中级、高级均可)
web前端开发(中级.高级均可) 工作地点:广东-深圳 工作年限:2年 学历要求:本科 招聘分类:前端开发工程师 工资范围:面议 招聘人数:3 发布日期:2014/07/29 截止日期:2014/08 ...
- VS 2010 WebSite网站 使用CodeBehide 方式开发[Web应用程序项目转Web网站]
由于生成Web应用程序的文件非常大,100M左右,上传到香港太慢,对于运维工作很不现实, 所以只能改用单个源代码文件上传方式,也就是Web网站方式,但VS2010中只提供Web网站转Web应用程序功能 ...
- 第一次正式java web开发项目的总结
去年下半年到现在,因为公司人员流动,也有好几个新进的员工分给我来带领,也有刚从学校出来的,在和他们交流的过程中,不由的想起自己刚刚进入这行的一些感想. 记得自己当初写过一篇总结的,我想这些对于刚出校门 ...
随机推荐
- 【BZOJ2434】阿狸的打字机(AC自动机,树状数组)
[BZOJ2434]阿狸的打字机(AC自动机,树状数组) 先写个暴力: 每次打印出字符串后,就插入到\(Trie\)树中 搞完后直接搭\(AC\)自动机 看一看匹配是怎么样的: 每次沿着\(AC\)自 ...
- 几道很Interesting的偏序问题
若干道偏序问题(STL,分块) 找了4道题目 BZOJ陌上花开(权限题,提供洛谷链接) Cogs2479偏序 Cogs2580偏序II Cogs2639偏序++ 作为一个正常人,肯定先看三维偏序 做法 ...
- 【BZOJ4736】温暖会指引我们前行(Link-Cut Tree)
[BZOJ4736]温暖会指引我们前行(Link-Cut Tree) ##题面 神TM题面是UOJ的 题解 LCT傻逼维护最大生成树 不会的可以去做一做魔法森林 #include<iostrea ...
- bzoj2157
LCT板子,打个lazy即可 # include <stdio.h> # include <stdlib.h> # include <iostream> # inc ...
- 关系型数据库工作原理-事务管理(二)(翻译自Coding-Geek文章)
本文翻译自Coding-Geek文章:< How does a relational database work>. 原文链接:http://coding-geek.com/how-dat ...
- 读 《 Web 研发模式的演变 》与《Javascript:世纪机器语言》
读了两篇文章,内心还是很震撼的,在这之前,我学习知识都是直接找教程,翻阅资料,写几个小demo,没有去了解我所学的东西的发展历程,<Web研发模式的演变>这篇文章讲述了web的前世今 ...
- Oracle用户、授权、角色管理
创建和删除用户是Oracle用户管理中的常见操作,但这其中隐含了Oracle数据库系统的系统权限与对象权限方面的知识.掌握还Oracle用户的授权操作和原理,可以有效提升我们的工作效率. Oracle ...
- SqlServer之like、charindex、patindex 在有无索引的情况下分析
1.环境介绍 测试环境 SQL2005 测试数据 200W条 2.环境准备 2.1建表 CREATE TABLE [dbo].[Depratments]( [Dep_id] [int] ...
- 用Canvas生成随机验证码(后端前端都可以)
一 .使用前端生成验证码 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> ...
- Python的Argparse模块是什么?(未完)
近日在阅读代码的过程中遇到了Argparse模块,记得前段时间已经看了,可是过了两周现在又忘了, 看来写代码一定要钻研到底搞清楚其中原委才行,本文主要参考Python3.6系列官方文档 ...