最近使用django2.0做网站,开发验证码功能时发现对于我的需求,django-simple-captcha有一些多余的功能,所以从它的源代码中提取了一部分加以修改,生成了一个简单的验证码模块。
在使用django-simple-captcha时发现了一个Bug,该库使用HttpResponse.write(image_data)的方式返回验证码图片数据,导致验证码图片无法显示,具体原因还不清楚,不过换成HttpResponse(content=image_data)的方式就好了。这也是我自定义验证码功能的原因。
所以我们在使用django的HttpResponse时,最好使用较稳定的HttpResponse(content=image_data)的方式,而不要使用HttpResponse.write(image_data)。
cn_char = '一乙二十丁厂七卜人入八九几儿了力乃刀又三于干亏士工土才寸下大丈与万上小口巾山千乞川亿个勺久凡及夕丸么广亡门义之尸弓己已子卫也女飞刃习叉马乡丰王井开夫天无元专云扎艺木五支厅不太犬区历尤友匹车巨牙屯比互切瓦止少日中冈贝内水见午牛手毛气升长仁什片仆化仇币仍仅斤爪反介父从今凶分乏公仓月氏勿欠风丹匀乌凤勾文六方火为斗忆订计户认心尺引丑巴孔队办以允予劝双书幻玉刊示末未击打巧正扑扒功扔去甘世古节本术汇头汉宁穴它讨写让礼训必议讯记永司尼民出汁丙左厉右石布龙平灭轧东卡北占业旧帅归且旦目叶甲申叮电号田由史只央兄叼叫另叨叹四生失禾丘付仗代仙们仪白仔他斥瓜乎丛令用甩印乐句匆册犯外处冬鸟务包饥主市立闪兰半辽奶奴加召皮边发孕圣对台矛纠母幼丝可式刑动扛寺吉扣考托老执巩圾扩扫地扬场耳共芒亚芝朽朴机权过臣再协西压厌在有百存而页匠夸夺灰达列死成夹轨邪划迈毕至此贞师尘尖劣光当早吐吓虫曲团同吊吃因吸吗屿帆岁回岂刚则肉网年朱先丢舌竹迁乔伟传乒乓休伍伏优伐延件任伤价份华仰仿伙伪自血向似后行舟全会杀合兆企众爷伞创肌朵杂危旬旨负各名多争色壮冲冰庄庆亦刘齐交次衣产决充妄闭问闯羊并关米灯州汗污江池汤忙兴宇守宅字安讲军许论农讽设访寻那迅尽导异孙阵阳收阶阴防奸如妇好她妈戏羽观欢买红纤级约纪驰巡寿弄麦形进戒吞远违运扶抚坛技坏扰拒找批扯址走抄坝贡攻赤折抓扮抢孝均抛投坟抗坑坊抖护壳志扭块声把报却劫芽花芹芬苍芳严芦劳克苏杆杠杜材村杏极李杨求更束豆两丽医辰励否还歼来连步坚旱盯呈时吴助县里呆园旷围呀吨足邮男困吵串员听吩吹呜吧吼别岗帐财针钉告我乱利秃秀私每兵估体何但伸作伯伶佣低你住位伴身皂佛近彻役返余希坐谷妥含邻岔肝肚肠龟免狂犹角删条卵岛迎饭饮系言冻状亩况床库疗应冷这序辛弃冶忘闲间闷判灶灿弟汪沙汽沃泛沟没沈沉怀忧快完宋宏牢究穷灾良证启评补初社识诉诊词译君灵即层尿尾迟局改张忌际陆阿陈阻附妙妖妨努忍劲鸡驱纯纱纳纲驳纵纷纸纹纺驴纽奉玩环武青责现表规抹拢拔拣担坦押抽拐拖拍者顶拆拥抵拘势抱垃拉拦拌幸招坡披拨择抬其取苦若茂苹苗英范直茄茎茅林枝杯柜析板松枪构杰述枕丧或画卧事刺枣雨卖矿码厕奔奇奋态欧垄妻轰顷转斩轮软到非叔肯齿些虎虏肾贤尚旺具果味昆国昌畅明易昂典固忠咐呼鸣咏呢岸岩帖罗帜岭凯败贩购图钓制知垂牧物乖刮秆和季委佳侍供使例版侄侦侧凭侨佩货依的迫质欣征往爬彼径所舍金命斧爸采受乳贪念贫肤肺肢肿胀朋股肥服胁周昏鱼兔狐忽狗备饰饱饲变京享店夜庙府底剂郊废净盲放刻育闸闹郑券卷单炒炊炕炎炉沫浅法泄河沾泪油泊沿泡注泻泳泥沸波泼泽治怖性怕怜怪学宝宗定宜审宙官空帘实试郎诗肩房诚衬衫视话诞询该详建肃录隶居届刷屈弦承孟孤陕降限妹姑姐姓始驾参艰线练组细驶织终驻驼绍经贯奏春帮珍玻毒型挂封持项垮挎城挠政赴赵挡挺括拴拾挑指垫挣挤拼挖按挥挪某甚革荐巷带草茧茶荒茫荡荣故胡南药标枯柄栋相查柏柳柱柿栏树要咸威歪研砖厘厚砌砍面耐耍牵残殃轻鸦皆背战点临览竖省削尝是盼眨哄显哑冒映星昨畏趴胃贵界虹虾蚁思蚂虽品咽骂哗咱响哈咬咳哪炭峡罚贱贴骨钞钟钢钥钩卸缸拜看矩怎牲选适秒香种秋科重复竿段便俩贷顺修保促侮俭俗俘信皇泉鬼侵追俊盾待律很须叙剑逃食盆胆胜胞胖脉勉狭狮独狡狱狠贸怨急饶蚀饺饼弯将奖哀亭亮度迹庭疮疯疫疤姿亲音帝施闻阀阁差养美姜叛送类迷前首逆总炼炸炮烂剃洁洪洒浇浊洞测洗活派洽染济洋洲浑浓津恒恢恰恼恨举觉宣室宫宪突穿窃客冠语扁袄祖神祝误诱说诵垦退既屋昼费陡眉孩除险院娃姥姨姻娇怒架贺盈勇怠柔垒绑绒结绕骄绘给络骆绝绞统耕耗艳泰珠班素蚕顽盏匪捞栽捕振载赶起盐捎捏埋捉捆捐损都哲逝捡换挽热恐壶挨耻耽恭莲莫荷获晋恶真框桂档桐株桥桃格校核样根索哥速逗栗配翅辱唇夏础破原套逐烈殊顾轿较顿毙致柴桌虑监紧党晒眠晓鸭晃晌晕蚊哨哭恩唤啊唉罢峰圆贼贿钱钳钻铁铃铅缺氧特牺造乘敌秤租积秧秩称秘透笔笑笋债借值倚倾倒倘俱倡候俯倍倦健臭射躬息徒徐舰舱般航途拿爹爱颂翁脆脂胸胳脏胶脑狸狼逢留皱饿恋桨浆衰高席准座脊症病疾疼疲效离唐资凉站剖竞部旁旅畜阅羞瓶拳粉料益兼烤烘烦烧烛烟递涛浙涝酒涉消浩海涂浴浮流润浪浸涨烫涌悟悄悔悦害宽家宵宴宾窄容宰案请朗诸读扇袜袖袍被祥课谁调冤谅谈谊剥恳展剧屑弱陵陶陷陪娱娘通能难预桑绢绣验继球理捧堵描域掩捷排掉堆推掀授教掏掠培接控探据掘职基著勒黄萌萝菌菜萄菊萍菠营械梦梢梅检梳梯桶救副票戚爽聋袭盛雪辅辆虚雀堂常匙晨睁眯眼悬野啦晚啄距跃略蛇累唱患唯崖崭崇圈铜铲银甜梨犁移笨笼笛符第敏做袋悠偿偶偷您售停偏假得衔盘船斜盒鸽悉欲彩领脚脖脸脱象够猜猪猎猫猛馅馆凑减毫麻痒痕廊康庸鹿盗章竟商族旋望率着盖粘粗粒断剪兽清添淋淹渠渐混渔淘液淡深婆梁渗情惜惭悼惧惕惊惨惯寇寄宿窑密谋谎祸谜逮敢屠弹随蛋隆隐婚婶颈绩绪续骑绳维绵绸绿琴斑替款堪搭塔越趁趋超提堤博揭喜插揪搜煮援裁搁搂搅握揉斯期欺联散惹葬葛董葡敬葱落朝辜葵棒棋植森椅椒棵棍棉棚棕惠惑逼厨厦硬确雁殖裂雄暂雅辈悲紫辉敞赏掌晴暑最量喷晶喇遇喊景践跌跑遗蛙蛛蜓喝喂喘喉幅帽赌赔黑铸铺链销锁锄锅锈锋锐短智毯鹅剩稍程稀税筐等筑策筛筒答筋筝傲傅牌堡集焦傍储奥街惩御循艇舒番释禽腊脾腔鲁猾猴然馋装蛮就痛童阔善羡普粪尊道曾焰港湖渣湿温渴滑湾渡游滋溉愤慌惰愧愉慨割寒富窜窝窗遍裕裤裙谢谣谦属屡强粥疏隔隙絮嫂登缎缓编骗缘瑞魂肆摄摸填搏塌鼓摆携搬摇搞塘摊蒜勤鹊蓝墓幕蓬蓄蒙蒸献禁楚想槐榆楼概赖酬感碍碑碎碰碗碌雷零雾雹输督龄鉴睛睡睬鄙愚暖盟歇暗照跨跳跪路跟遣蛾蜂嗓置罪罩错锡锣锤锦键锯矮辞稠愁筹签简毁舅鼠催傻像躲微愈遥腰腥腹腾腿触解酱痰廉新韵意粮数煎塑慈煤煌满漠源滤滥滔溪溜滚滨粱滩慎誉塞谨福群殿辟障嫌嫁叠缝缠静碧璃墙撇嘉摧截誓境摘摔聚蔽慕暮蔑模榴榜榨歌遭酷酿酸磁愿需弊裳颗嗽蜻蜡蝇蜘赚锹锻舞稳算箩管僚鼻魄貌膜膊膀鲜疑馒裹敲豪膏遮腐瘦辣竭端旗精歉熄熔漆漂漫滴演漏慢寨赛察蜜谱嫩翠熊凳骡缩慧撕撒趣趟撑播撞撤增聪鞋蕉蔬横槽樱橡飘醋醉震霉瞒题暴瞎影踢踏踩踪蝶蝴嘱墨镇靠稻黎稿稼箱箭篇僵躺僻德艘膝膛熟摩颜毅糊遵潜潮懂额慰劈操燕薯薪薄颠橘整融醒餐嘴蹄器赠默镜赞篮邀衡膨雕磨凝辨辩糖糕燃澡激懒壁避缴戴擦鞠藏霜霞瞧蹈螺穗繁辫赢糟糠燥臂翼骤鞭覆蹦镰翻鹰警攀蹲颤瓣爆疆壤耀躁嚼嚷籍魔灌蠢霸露囊罐次常用字笔画顺序表匕刁丐歹戈夭仑讥冗邓凫妆亥汛讳讶讹讼诀弛阱驮驯纫玖玛韧抠扼汞扳抡坎坞抑拟抒芙芜苇芥芯芭杖杉巫杈甫匣轩卤肖吱吠呕呐吟呛吻吭邑囤吮岖玫卦坷坯拓坪坤拄拧拂拙拇拗茉昔苛苫苟苞茁苔枉枢枚枫杭郁矾奈奄殴歧卓昙哎咕呵咙呻咒咆咖帕账贬贮氛秉岳侠侥侣侈卑刽刹肴觅忿瓮肮肪狞庞疟疙疚卒氓炬沽沮泣泞泌沼怔怯宠宛衩祈诡帚屉弧弥陋陌函姆虱叁绅驹绊绎契贰玷玲珊拭拷拱挟垢垛拯荆茸茬荚茵茴荞荠荤荧荔栈柑栅柠枷勃柬砂泵砚鸥轴韭虐昧盹咧昵昭盅勋哆咪哟幽钙钝钠钦钧钮毡氢秕俏俄俐侯徊衍胚胧胎狰饵峦奕咨飒闺闽籽娄烁炫洼柒涎洛恃恍恬恤宦诫诬祠诲屏屎逊陨姚娜蚤骇耘耙秦匿埂捂捍袁捌挫挚捣捅埃耿聂荸莽莱莉莹莺梆栖桦栓桅桩贾酌砸砰砾殉逞哮唠哺剔蚌蚜畔蚣蚪蚓哩圃鸯唁哼唆峭唧峻赂赃钾铆氨秫笆俺赁倔殷耸舀豺豹颁胯胰脐脓逛卿鸵鸳馁凌凄衷郭斋疹紊瓷羔烙浦涡涣涤涧涕涩悍悯窍诺诽袒谆祟恕娩骏琐麸琉琅措捺捶赦埠捻掐掂掖掷掸掺勘聊娶菱菲萎菩萤乾萧萨菇彬梗梧梭曹酝酗厢硅硕奢盔匾颅彪眶晤曼晦冕啡畦趾啃蛆蚯蛉蛀唬啰唾啤傀躯兜衅徘徙舶舷舵敛翎脯逸凰猖祭烹庶庵痊阎阐眷焊焕鸿涯淑淌淮淆渊淫淳淤淀涮涵惦悴惋寂窒谍谐裆袱祷谒谓谚尉堕隅婉颇绰绷综绽缀巢琳琢琼揍堰揩揽揖彭揣搀搓壹搔葫募蒋蒂韩棱椰焚椎棺榔椭粟棘酣酥硝硫颊雳翘凿棠晰鼎喳遏晾畴跋跛蛔蜒蛤鹃喻啼喧嵌赋赎赐锉锌甥掰氮氯黍筏牍粤逾腌腋腕猩猬惫敦痘痢痪竣翔奠遂焙滞湘渤渺溃溅湃愕惶寓窖窘雇谤犀隘媒媚婿缅缆缔缕骚瑟鹉瑰搪聘斟靴靶蓖蒿蒲蓉楔椿楷榄楞楣酪碘硼碉辐辑频睹睦瞄嗜嗦暇畸跷跺蜈蜗蜕蛹嗅嗡嗤署蜀幌锚锥锨锭锰稚颓筷魁衙腻腮腺鹏肄猿颖煞雏馍馏禀痹廓痴靖誊漓溢溯溶滓溺寞窥窟寝褂裸谬媳嫉缚缤剿赘熬赫蔫摹蔓蔗蔼熙蔚兢榛榕酵碟碴碱碳辕辖雌墅嘁踊蝉嘀幔镀舔熏箍箕箫舆僧孵瘩瘟彰粹漱漩漾慷寡寥谭褐褪隧嫡缨撵撩撮撬擒墩撰鞍蕊蕴樊樟橄敷豌醇磕磅碾憋嘶嘲嘹蝠蝎蝌蝗蝙嘿幢镊镐稽篓膘鲤鲫褒瘪瘤瘫凛澎潭潦澳潘澈澜澄憔懊憎翩褥谴鹤憨履嬉豫缭撼擂擅蕾薛薇擎翰噩橱橙瓢磺霍霎辙冀踱蹂蟆螃螟噪鹦黔穆篡篷篙篱儒膳鲸瘾瘸糙燎濒憾懈窿缰壕藐檬檐檩檀礁磷瞭瞬瞳瞪曙蹋蟋蟀嚎赡镣魏簇儡徽爵朦臊鳄糜癌懦豁臀藕藤瞻嚣鳍癞瀑襟璧戳攒孽蘑藻鳖蹭蹬簸簿蟹靡癣羹鬓攘蠕巍鳞糯譬霹躏髓蘸镶瓤矗艾夯凸卢叭叽皿凹囚矢乍尔冯玄邦迂邢芋芍吏夷吁吕吆屹廷迄臼仲伦伊肋旭匈牡佑佃伺囱肛肘甸狈鸠彤灸刨庇吝庐闰兑灼沐沛汰沥沦汹沧沪忱诅诈罕屁坠妓姊妒纬啥啸崎逻崔崩婴赊铐铛铝铡铣铭矫秸秽笙笤偎蟥艾夯凸卢叭叽皿凹囚矢乍尔冯玄邦迂邢芋芍吏夷吁吕吆屹廷迄臼仲伦伊肋旭匈牡佑佃伺囱肛肘甸狈鸠彤灸刨庇吝庐闰兑灼沐沛汰沥沦汹沧沪忱诅诈罕屁坠妓姊妒纬啥啸崎逻崔崩婴赊铐铛铝铡铣铭矫秸秽笙笤偎裉'
链接: https://pan.baidu.com/s/1ooDLNrlibstPKkt7r_yPWQ 提取码: k64x
import time
import hashlib
from io import BytesIO as StringIO
from random import choice, randint, randrange
from PIL import Image, ImageFilter, ImageDraw, ImageFont
from mysite.cn_char import cn_char # 常用3602汉字
class Captcha:
# 登陆验证码配置项,请参考https://django-simple-captcha.readthedocs.io/en/latest/usage.html
CAPTCHA_FONT_SIZE = 22
CAPTCHA_IMAGE_SIZE = (130, 30)
CAPTCHA_TIMEOUT = 60
CAPTCHA_LENGTH = 4
CAPTCHA_SCALE = 1
CAPTCHA_PUNCTUATION = '_"\',.;:-'
CAPTCHA_BACKGROUND_COLOR = '#ffffff'
CAPTCHA_FOREGROUND_COLOR = '#001100'
CAPTCHA_FONT_PATH = ['mysite/fonts/NotoSansCJK-Bold.ttf',
'mysite/fonts/NotoSansCJK-Medium.ttf',
'mysite/fonts/NotoSansCJK-Regular.ttf']
CAPTCHA_NOISE_FUNCTIONS = ['noise_dots'] # 图像噪声生成器
CAPTCHA_CHALLENGE_FUNCT = 'random_num_char_challenge'
CAPTCHA_LETTER_ROTATION = (-35, 35) # 拉登变换参数,与图像倾斜幅度相关
if CAPTCHA_CHALLENGE_FUNCT == 'random_cn_challenge':
CAPTCHA_LETTER_ROTATION = (choice(range(35, 70)) * -1, choice(range(35, 70)))
CAPTCHA_FILTER_FUNCTIONS = ['post_smooth']
captcha_regex = {'random_cn_challenge': r'^[\u4E00-\u9FA5]{4}$',
'random_num_char_challenge': r'^[\da-zA-Z]{4}$',
'random_calculation_challenge': r'^(\d|[1-9]\d{1,2})$'}
MAX_RANDOM_KEY = 18446744073709551616 # 2 << 63
def random_cn_challenge(self):
# 中文验证码
captcha = ''.join([choice(cn_char) for i in range(4)])
return captcha, captcha
def random_num_char_challenge(self):
# 数字加字母验证码
captcha = ''.join([choice('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') + choice(['', ' ']) for i in range(3)])
captcha += choice('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
return captcha, captcha.replace(' ', '')
def random_calculation_challenge(self):
# 计算题验证码
a = choice(range(1, 101))
b = choice(range(1, a + 1))
return ('%s+%s=?' % (a, b), str(a + b)) if choice([0, 1]) else ('%s-%s=?' % (a, b), str(a - b))
def getsize(self, font, text):
if hasattr(font, 'getoffset'):
return tuple([x + y for x, y in zip(font.getsize(text), font.getoffset(text))])
else:
return font.getsize(text)
def makeimg(self, size):
if self.CAPTCHA_BACKGROUND_COLOR == "transparent":
image = Image.new('RGBA', size)
else:
image = Image.new('RGB', size, self.CAPTCHA_BACKGROUND_COLOR)
return image
def noise_arcs(self, draw, image):
size = image.size
draw.arc([-20, -20, size[0], 20], 0, 295, fill=self.CAPTCHA_FOREGROUND_COLOR)
draw.line([-20, 20, size[0] + 20, size[1] - 20], fill=self.CAPTCHA_FOREGROUND_COLOR)
draw.line([-20, 0, size[0] + 20, size[1]], fill=self.CAPTCHA_FOREGROUND_COLOR)
return draw
def noise_dots(self, draw, image):
size = image.size
for p in range(int(size[0] * size[1] * 0.1)):
draw.point((randint(0, size[0]), randint(0, size[1])), fill=self.CAPTCHA_FOREGROUND_COLOR)
return draw
def post_smooth(self, image):
return image.filter(ImageFilter.SMOOTH)
def create_hashkey(self):
text, result = getattr(self, self.CAPTCHA_CHALLENGE_FUNCT)()
key = '%s%s%s%s' % (randrange(0, self.MAX_RANDOM_KEY), time.time(), text, result)
return hashlib.sha1(key.encode('utf8')).hexdigest()
def create_image(self):
text, result = getattr(self, self.CAPTCHA_CHALLENGE_FUNCT)()
fontpath = choice(self.CAPTCHA_FONT_PATH)
font = ImageFont.truetype(fontpath, self.CAPTCHA_FONT_SIZE * self.CAPTCHA_SCALE)
size = self.CAPTCHA_IMAGE_SIZE
image = self.makeimg(size)
xpos = 2
charlist = []
for char in text:
if char in self.CAPTCHA_PUNCTUATION and len(charlist) >= 1:
charlist[-1] += char
else:
charlist.append(char)
distance_from_top = 4 # Distance of the drawn text from the top of the captcha image
for char in charlist:
fgimage = Image.new('RGB', size, self.CAPTCHA_FOREGROUND_COLOR)
charimage = Image.new('L', self.getsize(font, ' %s ' % char), '#000000')
chardraw = ImageDraw.Draw(charimage)
chardraw.text((0, 0), ' %s ' % char, font=font, fill='#ffffff')
charimage = charimage.rotate(randrange(*self.CAPTCHA_LETTER_ROTATION), expand=0, resample=Image.BICUBIC)
charimage = charimage.crop(charimage.getbbox())
maskimage = Image.new('L', size)
maskimage.paste(charimage, (xpos, distance_from_top, xpos + charimage.size[0], distance_from_top + charimage.size[1]))
size = maskimage.size
image = Image.composite(fgimage, image, maskimage)
xpos = xpos + 2 + charimage.size[0]
# centering captcha on the image
tmpimg = self.makeimg(size)
tmpimg.paste(image, (int((size[0] - xpos) / 2), int((size[1] - charimage.size[1]) / 2 - distance_from_top)))
image = tmpimg.crop((0, 0, size[0], size[1]))
draw = ImageDraw.Draw(image)
for f in self.CAPTCHA_NOISE_FUNCTIONS:
draw = getattr(self, f)(draw, image)
for f in self.CAPTCHA_FILTER_FUNCTIONS:
image = getattr(self, f)(image)
out = StringIO()
image.save(out, "PNG")
out.seek(0)
return result, out
from django.core.cache import cache
from django.http import JsonResponse, HttpResponse
from mysite.utils import Captcha
captcha = Captcha()
def is_hashkey(hashkey):
if len(hashkey) == 40:
try:
int(hashkey, 16)
except:
return False
return True
raise False
def captcha_hashkey(request):
'''生成新的验证码标识,删除旧的验证码'''
if is_hashkey(request.POST['hashkey']):
cache.delete('captcha_img:%s' % request.POST['hashkey'])
return JsonResponse({'code': '0', 'hashkey': captcha.create_hashkey()})
return JsonResponse({'code': '1', 'msg': '参数错误'})
def captcha_image(request, hashkey):
'''返回验证码图片,同时缓存验证码的值'''
captcha_content, captcha_image_file = captcha.create_image()
cache.set('captcha_img:%s' % hashkey, captcha_content, captcha.CAPTCHA_TIMEOUT)
response = HttpResponse(captcha_image_file.read(), content_type='image/png')
response['Content-length'] = captcha_image_file.tell()
return response
def check_captcha(request):
'''检查验证码是否正确'''
captcha = request.POST['captcha']
hashkey = request.POST['hashkey']
cache_captcha = cache.get('captcha_img:%s' % hashkey)
if not cache_captcha:
return JsonResponse({'code': '1', 'msg': '验证码已过期'})
elif cache_captcha != captcha:
return JsonResponse({'code': '2', 'msg': '验证码错误'})
else:
return JsonResponse({'code': '0', 'msg': '验证码正确'})
from django.urls import re_path
from mysite import views_captcha
urlpatterns = [
re_path(r'^captcha/hashkey/?$', views_captcha.captcha_hashkey),
re_path(r'^captcha/image/(\w+)/?$', views_captcha.captcha_image),
re_path(r'^captcha/check-captcha/?$', views_captcha.check_captcha),
]