-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontent.json
1 lines (1 loc) · 260 KB
/
content.json
1
{"meta":{"title":"z77z的小码窝","subtitle":"年少无为,卖码为生。","description":null,"author":"邹海清","url":"http://z77z.oschina.io"},"pages":[{"title":"","date":"2017-01-30T09:20:37.767Z","updated":"2016-12-30T18:16:02.523Z","comments":false,"path":"categories/index.html","permalink":"http://z77z.oschina.io/categories/index.html","excerpt":"","text":""},{"title":"index","date":"2017-02-17T15:11:38.942Z","updated":"2017-02-17T15:11:38.942Z","comments":true,"path":"personal_resume/index.html","permalink":"http://z77z.oschina.io/personal_resume/index.html","excerpt":"","text":"个人简历|邹海清html,body,div,p{margin:0;padding:0} body{font-size:14px;font-family:\"microsoft yahei\";background-color:#ffffff} table{border-collapse:collapse;border-spacing:0;table-layout:fixed} th,td{font-size:12px;padding:0} a{text-decoration:none} img{border:0 none} .chead{width:734px} .chead .logo{width:434px;height:90px} .chead .txt{width:150px;height:90px} .blue{text-decoration:none;color:#3c3d5d} .column{width:734px;line-height:28px;border:1px solid #dedede} .column .hbox{width:125px;height:154px} .column .head{display:block;background-color:#fafafa} .column .box{width:100%;background-color:#ffffff;border-top:2px solid #f2f3f5} .column .box1{width:100%;word-wrap:break-word;color:#ffffff;background-color:#3f4160} .column .box2{width:100%;background-color:#f8f9fa} .column .tba{width:702px;padding:0 15px 15px} .column .tbb{width:702px;padding:0 15px} .column .tb1{width:682px;line-height:28px;color:#333333;word-break:break-all;padding:0 10px} .column .tb2{width:331px;padding:0 10px} .column .tb3{width:682px;padding:15px 10px 15px 0} .column .gray,.column .gray2{color:#999999;word-break:break-all} .column .gray2{color:#666666} .column .plate1,.column .plate2{width:342px;height:54px;font-size:16px;font-weight:bold;color:#818ba3} .column .plate1{width:682px;padding:0 25px} .column .plate1 .f16{font-size:14px;font-weight:normal;color:#333333} .column .plate1 .f12{font-size:12px;font-weight:normal;color:#999999} .column .keys,.column .keys2{width:85px;line-height:28px;color:#666666} .column .keys2{width:100px} .column .txt1,.column .txt2,.column .txt3{width:597px;line-height:28px;color:#333333;word-break:break-all} .column .txt2{width:246px} .column .txt3{font-size:14px;font-weight:bold} .column .txt4{width:240px;color:#ffffff;word-break:break-all} .column .infr{width:587px;color:#ffffff;table-layout:auto} .column .vam,.column .grcha{vertical-align:middle;margin-left:5px} .column .box1 .vam{margin-right:5px;margin-left:0} .column .name{font-size:24px;padding-bottom:18px} .column .icard{color:#a1a3ae;padding-bottom:18px} .column .con{border-top:1px dotted #ddd} .column .pr20{width:165px;padding-right:5px} .column .time{width:120px;line-height:28px;color:#666666;padding-left:10px} .column .rtbox{width:557px;line-height:28px;color:#333333;padding:0 10px 0 5px;word-break:break-all} .column .hai,.column .guan{line-height:18px;font-size:12px;color:#ffffff;vertical-align:text-top;margin-left:5px;padding:1px 3px;background-color:#3cbe7f;border-radius:2px} .column .guan{background-color:#ff9f20} .column .tag{display:inline-block;word-break:break-all;#display:inline;#zoom:1} .column .all{color:#666;padding:10px 10px;background-color:#fafafa} .column .tit{width:682px;height:40px;color:#666666;padding:0 10px;background-color:#f5f5f5} .column .p15{padding:15px 0} .column .p5{display:inline-block;color:#666666;padding:0 5px;#display:inline;#zoom:1} .column .cell .skill{width:120px;font-size:14px;font-weight:bold;text-align:right;padding-right:5px} .column .cell .skbg,.column .cell .skco{display:inline-block;width:206px;height:18px;line-height:18px;font-size:12px;color:#ffffff;vertical-align:top;margin-top:6px;background-color:#dddddd;border-radius:20px;#display:inline;#zoom:1} .column .cell .skco{width:196px;font-style:normal;margin-top:0;padding-left:10px;background-color:#ff9f20;z-index:3} .column .sl .skco{width:147px} .column .lh .skco{width:98px} .column .yb .skco{width:49px} .column .fbox strong{font-size:14px;font-weight:bold} .column .cha{font-size:12px;font-weight:normal;color:#2d64b3;margin-left:5px} .column .cha:hover{color:#ff6000} .column .email{width:265px;color:#ffffff} .eng td,.eng .txt3,.eng .fbox strong{font-family:\"Arial\"} .eng .txt1,.eng .rtbox,.eng .phd{width:557px;font-family:\"Arial\";line-height:28px} .eng .txt2{width:206px} .eng .txt4{width:145px} .eng .cell .txt3{width:120px} .eng .keys{width:110px;text-align:right;padding-right:15px} .eng .pr20 .keys{width:50px} .eng .keys2{width:80px} .eng .phd{padding-left:135px} .eng .time{width:110px;text-align:right;padding-right:10px} .eng .email{width:170px} 邹海清 目前正在找工作 18482173147 [email protected] 男|22 岁 (1994/06/01)|现居住成都-武侯区|2年工作经验 最近工作 职位: java初级软件工程师 公司: 四川广昱天恒教育咨询股份有限公司 行业: 教育/培训/院校 最高学历/学位 专业: 通信工程 学校: 电子科技大学成都学院 学历/学位: 大专 个人信息 户口/国籍: 南充 婚姻状况: 未婚 目前年收入 6万元 (包含基本工资、补贴、奖金、股权收益等) 求职意向 个人标签: java mysql jquery mui springmvc springboot redis shiro mybatisplus 学习ing 期望薪资: 6000-7999 元/月 地点: 成都 职能: 软件工程师 行业: 计算机软件 到岗时间: 1个月内 工作类型: 全职 自我评价: 求知欲及自我学习能力强,热衷软件开发,喜欢思考,积极向上,热爱生活。平时喜欢在论坛或博客上自学软件编程语言。自己也热衷于软件开源,每周至少发布一篇技术博客,和其他开发人员进行交流学习。不善于制造轮子,但善于运用和整合轮子。just do it!个人博客:  http://z77z.oschina.io/码云git:  https://git.oschina.net/z77z开源中国博客:  https://my.oschina.net/z707z 工作经验 2015/11-至今 java初级软件工程师|开发部 四川广昱天恒教育咨询股份有限公司 [1年3个月] 教育/培训/院校|150-500人|民营公司 工作描述: 进公司的时候刚刚开始采集需求,学习了web开发的全部流程,负责相关模块的开发和维护,从根据业务建立数据库逻辑,到前端页面的数据显示。学习新的框架,优化已有模块。 项目经验 2016/12-至今 SpringBoot整合框架学习开源项目 项目描述: 本人开源的一个开源项目,利用空闲时间学习的一个项目,每周都会随博客更新,功能如下:1.美女图片爬虫2.mybatisplus的整合,完成了基本的demo实现3.整个项目的核心是springBoot,每个框架插件的使用都是基于springBoot的整合。4.redis和springBoot的整合,完成基本Demo。4.shiro的整合,和基于redis的安全管理功能的实现。还在继续完善中。。项目开源地址:https://git.oschina.net/z77z/springboot_mybatisplus项目具体的功能参考博客文章。 责任描述: 个人的周更开源项目。 2016/10-2016/12 天昱业务信息管理系统手机版 所属公司: 四川广昱天恒教育咨询股份有限公司 项目描述: 这个项目是使用mui作为前端框架,后台是基于pc网页端的后台编写的借口,也是http请求。因为mui是基于native.js集成,所以支持打包成Android包和iOS包,支持调用手机底层的接口,通过native.js获取手机唯一标识符,通过pc的管理控制台,保证一个账号只能在一个手机上登陆。 责任描述: 使用mui开发是由本人提出,所以我担任主要开发。将一些pc网页版中经常使用的功能,编写相应的接口,前端使用mui开发,具备手机app常用功能,比如下拉刷新,侧滑菜单栏,手机通讯录添加,等等。最后打包成app发布。 2016/1-2016/10 天昱业务信息管理系统 所属公司: 四川广昱天恒教育咨询股份有限公司 项目描述: 项目核心采用spring+springmvc+mybatis框架整合开发。数据库使用mysql,业务流程使用activity框架,前端使用bootstap开发,使用springsecrit安全认证权限管理框架。此系统具备oa的基本管理功能,客户管理,财务管理,合同管理,项目管理,流程管理等等。 责任描述: 本人主要负责字典管理模块:包括数据库字典的增删改查;用户组管理模块:相应的增删改查;合同管理模块:合同的高级查询,对合同字段级别的权限控制,不同角色查看的合同字段不一样,合同的详情显示,合同的金额变更记录,收款记录,退款记录;项目管理模块:各类项目的列表显示,项目的分配,各类项目分配流程不一样,提供分配方案;在各个部门讨论收集需求。运用的技术主要有:spring,springmvc,mybatis,jQuery,jqGrid,jsp,jstl自定义标签,bootstap,ajax,fastjson等等。 教育经历 2013/9-2016/6 电子科技大学成都学院 大专|通信工程 在校情况 校内荣誉 2015/11 院级优秀毕业生 2015/6 国家奖学金(三等) 技能特长 技能/语言 JavaScript 熟练 Java 熟练"},{"title":"","date":"2017-01-30T09:20:37.773Z","updated":"2016-12-30T18:15:44.003Z","comments":false,"path":"tags/index.html","permalink":"http://z77z.oschina.io/tags/index.html","excerpt":"","text":""},{"title":"404","date":"2017-01-30T09:20:37.762Z","updated":"2016-12-30T18:29:31.343Z","comments":true,"path":"404.html","permalink":"http://z77z.oschina.io/404.html","excerpt":"","text":"button,hr,input{overflow:visible}audio,canvas,progress,video{display:inline-block}progress,sub,sup{vertical-align:baseline}html{font-family:sans-serif;line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0} menu,article,aside,details,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{}button,select{text-transform:none}[type=submit], [type=reset],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:ButtonText dotted 1px}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}[hidden],template{display:none}/*# sourceMappingURL=normalize.min.css.map */ body { background: #2196F3; } body h1 { position: fixed; text-align: center; bottom: 10px; left: 30px; color: white; font-size: 20px; font-family: Roboto; font-weight: normal; z-index: 999; } body h1 span { font-size: 15px; font-weight: lighter; } body h1 a { color: white; font-size: 15px; padding: 0 30px; } #notFound { position: fixed; top: 50%; left: 50%; transform: translateY(-50%) translateX(-50%) scale(1.2); width: 80%; height: auto; } /** * @license * pixi.js - v2.2.5 * Copyright (c) 2012-2014, Mat Groves * http://goodboydigital.com/ * * Compiled: 2015-01-27 * * pixi.js is licensed under the MIT License. * http://www.opensource.org/licenses/mit-license.php */ (function(){var a=this,b=b||{};b.WEBGL_RENDERER=0,b.CANVAS_RENDERER=1,b.VERSION=\"v2.2.5\",b.blendModes={NORMAL:0,ADD:1,MULTIPLY:2,SCREEN:3,OVERLAY:4,DARKEN:5,LIGHTEN:6,COLOR_DODGE:7,COLOR_BURN:8,HARD_LIGHT:9,SOFT_LIGHT:10,DIFFERENCE:11,EXCLUSION:12,HUE:13,SATURATION:14,COLOR:15,LUMINOSITY:16},b.scaleModes={DEFAULT:0,LINEAR:0,NEAREST:1},b._UID=0,\"undefined\"!=typeof Float32Array?(b.Float32Array=Float32Array,b.Uint16Array=Uint16Array,b.Uint32Array=Uint32Array,b.ArrayBuffer=ArrayBuffer):(b.Float32Array=Array,b.Uint16Array=Array),b.INTERACTION_FREQUENCY=30,b.AUTO_PREVENT_DEFAULT=!0,b.PI_2=2*Math.PI,b.RAD_TO_DEG=180/Math.PI,b.DEG_TO_RAD=Math.PI/180,b.RETINA_PREFIX=\"@2x\",b.dontSayHello=!1,b.defaultRenderOptions={view:null,transparent:!1,antialias:!1,preserveDrawingBuffer:!1,resolution:1,clearBeforeRender:!0,autoResize:!1},b.sayHello=function(a){if(!b.dontSayHello){if(navigator.userAgent.toLowerCase().indexOf(\"chrome\")>-1){var c=[\"%c %c %c Pixi.js \"+b.VERSION+\" - \"+a+\" %c %c http://www.pixijs.com/ %c %c ♥%c♥%c♥ \",\"background: #ff66a5\",\"background: #ff66a5\",\"color: #ff66a5; background: #030307;\",\"background: #ff66a5\",\"background: #ffc3dc\",\"background: #ff66a5\",\"color: #ff2424; background: #fff\",\"color: #ff2424; background: #fff\",\"color: #ff2424; background: #fff\"];console.log.apply(console,c)}else window.console&&console.log(\"Pixi.js \"+b.VERSION+\" - http://www.pixijs.com/\");b.dontSayHello=!0}},b.Point=function(a,b){this.x=a||0,this.y=b||0},b.Point.prototype.clone=function(){return new b.Point(this.x,this.y)},b.Point.prototype.set=function(a,b){this.x=a||0,this.y=b||(0!==b?this.x:0)},b.Point.prototype.constructor=b.Point,b.Rectangle=function(a,b,c,d){this.x=a||0,this.y=b||0,this.width=c||0,this.height=d||0},b.Rectangle.prototype.clone=function(){return new b.Rectangle(this.x,this.y,this.width,this.height)},b.Rectangle.prototype.contains=function(a,b){if(this.widthe;f=e++){var g=this.points[2*e],h=this.points[2*e+1],i=this.points[2*f],j=this.points[2*f+1],k=h>b!=j>b&&(i-g)*(b-h)/(j-h)+g>a;k&&(c=!c)}return c},b.Polygon.prototype.constructor=b.Polygon,b.Circle=function(a,b,c){this.x=a||0,this.y=b||0,this.radius=c||0},b.Circle.prototype.clone=function(){return new b.Circle(this.x,this.y,this.radius)},b.Circle.prototype.contains=function(a,b){if(this.radius=c+d},b.Circle.prototype.getBounds=function(){return new b.Rectangle(this.x-this.radius,this.y-this.radius,2*this.radius,2*this.radius)},b.Circle.prototype.constructor=b.Circle,b.Ellipse=function(a,b,c,d){this.x=a||0,this.y=b||0,this.width=c||0,this.height=d||0},b.Ellipse.prototype.clone=function(){return new b.Ellipse(this.x,this.y,this.width,this.height)},b.Ellipse.prototype.contains=function(a,b){if(this.widtha||a>=this.children.length)throw new Error(\"getChildAt: Supplied index \"+a+\" does not exist in the child list, or the supplied DisplayObject must be a child of the caller\");return this.children[a]},b.DisplayObjectContainer.prototype.removeChild=function(a){var b=this.children.indexOf(a);if(-1!==b)return this.removeChildAt(b)},b.DisplayObjectContainer.prototype.removeChildAt=function(a){var b=this.getChildAt(a);return this.stage&&b.removeStageReference(),b.parent=void 0,this.children.splice(a,1),b},b.DisplayObjectContainer.prototype.removeChildren=function(a,b){var c=a||0,d=\"number\"==typeof b?b:this.children.length,e=d-c;if(e>0&&d>=e){for(var f=this.children.splice(c,e),g=0;ga;a++)this.children[a].updateTransform()},b.DisplayObjectContainer.prototype.displayObjectContainerUpdateTransform=b.DisplayObjectContainer.prototype.updateTransform,b.DisplayObjectContainer.prototype.getBounds=function(){if(0===this.children.length)return b.EmptyRectangle;for(var a,c,d,e=1/0,f=1/0,g=-1/0,h=-1/0,i=!1,j=0,k=this.children.length;k>j;j++){var l=this.children[j];l.visible&&(i=!0,a=this.children[j].getBounds(),e=ed?h:d)}if(!i)return b.EmptyRectangle;var m=this._bounds;return m.x=e,m.y=f,m.width=g-e,m.height=h-f,m},b.DisplayObjectContainer.prototype.getLocalBounds=function(){var a=this.worldTransform;this.worldTransform=b.identityMatrix;for(var c=0,d=this.children.length;d>c;c++)this.children[c].updateTransform();var e=this.getBounds();return this.worldTransform=a,e},b.DisplayObjectContainer.prototype.setStageReference=function(a){this.stage=a,this._interactive&&(this.stage.dirty=!0);for(var b=0,c=this.children.length;c>b;b++){var d=this.children[b];d.setStageReference(a)}},b.DisplayObjectContainer.prototype.removeStageReference=function(){for(var a=0,b=this.children.length;b>a;a++){var c=this.children[a];c.removeStageReference()}this._interactive&&(this.stage.dirty=!0),this.stage=null},b.DisplayObjectContainer.prototype._renderWebGL=function(a){if(this.visible&&!(this.alphab;b++)this.children[b]._renderWebGL(a);a.spriteBatch.stop(),this._mask&&a.maskManager.popMask(this._mask,a),this._filters&&a.filterManager.popFilter(),a.spriteBatch.start()}else for(b=0,c=this.children.length;c>b;b++)this.children[b]._renderWebGL(a)}},b.DisplayObjectContainer.prototype._renderCanvas=function(a){if(this.visible!==!1&&0!==this.alpha){if(this._cacheAsBitmap)return void this._renderCachedSprite(a);this._mask&&a.maskManager.pushMask(this._mask,a);for(var b=0,c=this.children.length;c>b;b++){var d=this.children[b];d._renderCanvas(a)}this._mask&&a.maskManager.popMask(a)}},b.Sprite=function(a){b.DisplayObjectContainer.call(this),this.anchor=new b.Point,this.texture=a||b.Texture.emptyTexture,this._width=0,this._height=0,this.tint=16777215,this.blendMode=b.blendModes.NORMAL,this.shader=null,this.texture.baseTexture.hasLoaded?this.onTextureUpdate():this.texture.on(\"update\",this.onTextureUpdate.bind(this)),this.renderable=!0},b.Sprite.prototype=Object.create(b.DisplayObjectContainer.prototype),b.Sprite.prototype.constructor=b.Sprite,Object.defineProperty(b.Sprite.prototype,\"width\",{get:function(){return this.scale.x*this.texture.frame.width},set:function(a){this.scale.x=a/this.texture.frame.width,this._width=a}}),Object.defineProperty(b.Sprite.prototype,\"height\",{get:function(){return this.scale.y*this.texture.frame.height},set:function(a){this.scale.y=a/this.texture.frame.height,this._height=a}}),b.Sprite.prototype.setTexture=function(a){this.texture=a,this.cachedTint=16777215},b.Sprite.prototype.onTextureUpdate=function(){this._width&&(this.scale.x=this._width/this.texture.frame.width),this._height&&(this.scale.y=this._height/this.texture.frame.height)},b.Sprite.prototype.getBounds=function(a){var b=this.texture.frame.width,c=this.texture.frame.height,d=b*(1-this.anchor.x),e=b*-this.anchor.x,f=c*(1-this.anchor.y),g=c*-this.anchor.y,h=a||this.worldTransform,i=h.a,j=h.b,k=h.c,l=h.d,m=h.tx,n=h.ty,o=-1/0,p=-1/0,q=1/0,r=1/0;if(0===j&&0===k)0>i&&(i*=-1),0>l&&(l*=-1),q=i*e+m,o=i*d+m,r=l*g+n,p=l*f+n;else{var s=i*e+k*g+m,t=l*g+j*e+n,u=i*d+k*g+m,v=l*g+j*d+n,w=i*d+k*f+m,x=l*f+j*d+n,y=i*e+k*f+m,z=l*f+j*e+n;q=q>s?s:q,q=q>u?u:q,q=q>w?w:q,q=q>y?y:q,r=r>t?t:r,r=r>v?v:r,r=r>x?x:r,r=r>z?z:r,o=s>o?s:o,o=u>o?u:o,o=w>o?w:o,o=y>o?y:o,p=t>p?t:p,p=v>p?v:p,p=x>p?x:p,p=z>p?z:p}var A=this._bounds;return A.x=q,A.width=o-q,A.y=r,A.height=p-r,this._currentBounds=A,A},b.Sprite.prototype._renderWebGL=function(a){if(this.visible&&!(this.alphab;b++)this.children[b]._renderWebGL(a);d.stop(),this._mask&&a.maskManager.popMask(this._mask,a),this._filters&&a.filterManager.popFilter(),d.start()}else for(a.spriteBatch.render(this),b=0,c=this.children.length;c>b;b++)this.children[b]._renderWebGL(a)}},b.Sprite.prototype._renderCanvas=function(a){if(!(this.visible===!1||0===this.alpha||this.texture.crop.width0&&this.collectInteractiveSprite(f,f)):(f.__iParent=null,f.children.length>0&&this.collectInteractiveSprite(f,b))}},b.InteractionManager.prototype.setTarget=function(a){this.target=a,this.resolution=a.resolution,null===this.interactionDOMElement&&this.setTargetDomElement(a.view)},b.InteractionManager.prototype.setTargetDomElement=function(a){this.removeEvents(),window.navigator.msPointerEnabled&&(a.style[\"-ms-content-zooming\"]=\"none\",a.style[\"-ms-touch-action\"]=\"none\"),this.interactionDOMElement=a,a.addEventListener(\"mousemove\",this.onMouseMove,!0),a.addEventListener(\"mousedown\",this.onMouseDown,!0),a.addEventListener(\"mouseout\",this.onMouseOut,!0),a.addEventListener(\"touchstart\",this.onTouchStart,!0),a.addEventListener(\"touchend\",this.onTouchEnd,!0),a.addEventListener(\"touchmove\",this.onTouchMove,!0),window.addEventListener(\"mouseup\",this.onMouseUp,!0)},b.InteractionManager.prototype.removeEvents=function(){this.interactionDOMElement&&(this.interactionDOMElement.style[\"-ms-content-zooming\"]=\"\",this.interactionDOMElement.style[\"-ms-touch-action\"]=\"\",this.interactionDOMElement.removeEventListener(\"mousemove\",this.onMouseMove,!0),this.interactionDOMElement.removeEventListener(\"mousedown\",this.onMouseDown,!0),this.interactionDOMElement.removeEventListener(\"mouseout\",this.onMouseOut,!0),this.interactionDOMElement.removeEventListener(\"touchstart\",this.onTouchStart,!0),this.interactionDOMElement.removeEventListener(\"touchend\",this.onTouchEnd,!0),this.interactionDOMElement.removeEventListener(\"touchmove\",this.onTouchMove,!0),this.interactionDOMElement=null,window.removeEventListener(\"mouseup\",this.onMouseUp,!0))},b.InteractionManager.prototype.update=function(){if(this.target){var a=Date.now(),c=a-this.last;if(c=c*b.INTERACTION_FREQUENCY/1e3,!(1>c)){this.last=a;var d=0;this.dirty&&this.rebuildInteractiveGraph();var e=this.interactiveItems.length,f=\"inherit\",g=!1;for(d=0;e>d;d++){var h=this.interactiveItems[d];h.__hit=this.hitTest(h,this.mouse),this.mouse.target=h,h.__hit&&!g?(h.buttonMode&&(f=h.defaultCursor),h.interactiveChildren||(g=!0),h.__isOver||(h.mouseover&&h.mouseover(this.mouse),h.__isOver=!0)):h.__isOver&&(h.mouseout&&h.mouseout(this.mouse),h.__isOver=!1)}this.currentCursorStyle!==f&&(this.currentCursorStyle=f,this.interactionDOMElement.style.cursor=f)}}},b.InteractionManager.prototype.rebuildInteractiveGraph=function(){this.dirty=!1;for(var a=this.interactiveItems.length,b=0;a>b;b++)this.interactiveItems[b].interactiveChildren=!1;this.interactiveItems=[],this.stage.interactive&&this.interactiveItems.push(this.stage),this.collectInteractiveSprite(this.stage,this.stage)},b.InteractionManager.prototype.onMouseMove=function(a){this.dirty&&this.rebuildInteractiveGraph(),this.mouse.originalEvent=a;var b=this.interactionDOMElement.getBoundingClientRect();this.mouse.global.x=(a.clientX-b.left)*(this.target.width/b.width)/this.resolution,this.mouse.global.y=(a.clientY-b.top)*(this.target.height/b.height)/this.resolution;for(var c=this.interactiveItems.length,d=0;c>d;d++){var e=this.interactiveItems[d];e.mousemove&&e.mousemove(this.mouse)}},b.InteractionManager.prototype.onMouseDown=function(a){this.dirty&&this.rebuildInteractiveGraph(),this.mouse.originalEvent=a,b.AUTO_PREVENT_DEFAULT&&this.mouse.originalEvent.preventDefault();for(var c=this.interactiveItems.length,d=this.mouse.originalEvent,e=2===d.button||3===d.which,f=e?\"rightdown\":\"mousedown\",g=e?\"rightclick\":\"click\",h=e?\"__rightIsDown\":\"__mouseIsDown\",i=e?\"__isRightDown\":\"__isDown\",j=0;c>j;j++){var k=this.interactiveItems[j];if((k[f]||k[g])&&(k[h]=!0,k.__hit=this.hitTest(k,this.mouse),k.__hit&&(k[f]&&k[f](this.mouse),k[i]=!0,!k.interactiveChildren)))break}},b.InteractionManager.prototype.onMouseOut=function(a){this.dirty&&this.rebuildInteractiveGraph(),this.mouse.originalEvent=a;var b=this.interactiveItems.length;this.interactionDOMElement.style.cursor=\"inherit\";for(var c=0;b>c;c++){var d=this.interactiveItems[c];d.__isOver&&(this.mouse.target=d,d.mouseout&&d.mouseout(this.mouse),d.__isOver=!1)}this.mouseOut=!0,this.mouse.global.x=-1e4,this.mouse.global.y=-1e4},b.InteractionManager.prototype.onMouseUp=function(a){this.dirty&&this.rebuildInteractiveGraph(),this.mouse.originalEvent=a;for(var b=this.interactiveItems.length,c=!1,d=this.mouse.originalEvent,e=2===d.button||3===d.which,f=e?\"rightup\":\"mouseup\",g=e?\"rightclick\":\"click\",h=e?\"rightupoutside\":\"mouseupoutside\",i=e?\"__isRightDown\":\"__isDown\",j=0;b>j;j++){var k=this.interactiveItems[j];(k[g]||k[f]||k[h])&&(k.__hit=this.hitTest(k,this.mouse),k.__hit&&!c?(k[f]&&k[f](this.mouse),k[i]&&k[g]&&k[g](this.mouse),k.interactiveChildren||(c=!0)):k[i]&&k[h]&&k[h](this.mouse),k[i]=!1)}},b.InteractionManager.prototype.hitTest=function(a,c){var d=c.global;if(!a.worldVisible)return!1;a.worldTransform.applyInverse(d,this._tempPoint);var e,f=this._tempPoint.x,g=this._tempPoint.y;if(c.target=a,a.hitArea&&a.hitArea.contains)return a.hitArea.contains(f,g);if(a instanceof b.Sprite){var h,i=a.texture.frame.width,j=a.texture.frame.height,k=-i*a.anchor.x;if(f>k&&k+i>f&&(h=-j*a.anchor.y,g>h&&h+j>g))return!0}else if(a instanceof b.Graphics){var l=a.graphicsData;for(e=0;ee;e++){var o=a.children[e],p=this.hitTest(o,c);if(p)return c.target=a,!0}return!1},b.InteractionManager.prototype.onTouchMove=function(a){this.dirty&&this.rebuildInteractiveGraph();var b,c=this.interactionDOMElement.getBoundingClientRect(),d=a.changedTouches,e=0;for(e=0;e16&255)/255,(a>>8&255)/255,(255&a)/255]},b.rgb2hex=function(a){return(255*a[0]0;)(c[d]===b||c[d]._originalHandler===b)&&c.splice(d,1);return 0===c.length&&delete this._listeners[a],this},a.removeAllListeners=function(a){return this._listeners=this._listeners||{},this._listeners[a]?(delete this._listeners[a],this):this}}},b.Event=function(a,b,c){this.__isEventObject=!0,this.stopped=!1,this.stoppedImmediate=!1,this.target=a,this.type=b,this.data=c,this.content=c,this.timeStamp=Date.now()},b.Event.prototype.stopPropagation=function(){this.stopped=!0},b.Event.prototype.stopImmediatePropagation=function(){this.stoppedImmediate=!0},b.autoDetectRenderer=function(a,c,d){a||(a=800),c||(c=600);var e=function(){try{var a=document.createElement(\"canvas\");return!!window.WebGLRenderingContext&&(a.getContext(\"webgl\")||a.getContext(\"experimental-webgl\"))}catch(b){return!1}}();return e?new b.WebGLRenderer(a,c,d):new b.CanvasRenderer(a,c,d)},b.autoDetectRecommendedRenderer=function(a,c,d){a||(a=800),c||(c=600);var e=function(){try{var a=document.createElement(\"canvas\");return!!window.WebGLRenderingContext&&(a.getContext(\"webgl\")||a.getContext(\"experimental-webgl\"))}catch(b){return!1}}(),f=/Android/i.test(navigator.userAgent);return e&&!f?new b.WebGLRenderer(a,c,d):new b.CanvasRenderer(a,c,d)},b.PolyK={},b.PolyK.Triangulate=function(a){var c=!0,d=a.length>>1;if(3>d)return[];for(var e=[],f=[],g=0;d>g;g++)f.push(g);g=0;for(var h=d;h>3;){var i=f[(g+0)%h],j=f[(g+1)%h],k=f[(g+2)%h],l=a[2*i],m=a[2*i+1],n=a[2*j],o=a[2*j+1],p=a[2*k],q=a[2*k+1],r=!1;if(b.PolyK._convex(l,m,n,o,p,q,c)){r=!0;for(var s=0;h>s;s++){var t=f[s];if(t!==i&&t!==j&&t!==k&&b.PolyK._PointInTriangle(a[2*t],a[2*t+1],l,m,n,o,p,q)){r=!1;break}}}if(r)e.push(i,j,k),f.splice((g+1)%h,1),h--,g=0;else if(g++>3*h){if(!c)return null;for(e=[],f=[],g=0;d>g;g++)f.push(g);g=0,h=d,c=!1}}return e.push(f[0],f[1],f[2]),e},b.PolyK._PointInTriangle=function(a,b,c,d,e,f,g,h){var i=g-c,j=h-d,k=e-c,l=f-d,m=a-c,n=b-d,o=i*i+j*j,p=i*k+j*l,q=i*m+j*n,r=k*k+l*l,s=k*m+l*n,t=1/(o*r-p*p),u=(r*q-p*s)*t,v=(o*s-p*q)*t;return u>=0&&v>=0&&1>u+v},b.PolyK._convex=function(a,b,c,d,e,f,g){return(b-d)*(e-c)+(c-a)*(f-d)>=0===g},b.initDefaultShaders=function(){},b.CompileVertexShader=function(a,c){return b._CompileShader(a,c,a.VERTEX_SHADER)},b.CompileFragmentShader=function(a,c){return b._CompileShader(a,c,a.FRAGMENT_SHADER)},b._CompileShader=function(a,b,c){var d=b.join(\"\\n\"),e=a.createShader(c);return a.shaderSource(e,d),a.compileShader(e),a.getShaderParameter(e,a.COMPILE_STATUS)?e:(window.console.log(a.getShaderInfoLog(e)),null)},b.compileProgram=function(a,c,d){var e=b.CompileFragmentShader(a,d),f=b.CompileVertexShader(a,c),g=a.createProgram();return a.attachShader(g,f),a.attachShader(g,e),a.linkProgram(g),a.getProgramParameter(g,a.LINK_STATUS)||window.console.log(\"Could not initialise shaders\"),g},b.PixiShader=function(a){this._UID=b._UID++,this.gl=a,this.program=null,this.fragmentSrc=[\"precision lowp float;\",\"varying vec2 vTextureCoord;\",\"varying vec4 vColor;\",\"uniform sampler2D uSampler;\",\"void main(void) {\",\" gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;\",\"}\"],this.textureCount=0,this.firstRun=!0,this.dirty=!0,this.attributes=[],this.init()},b.PixiShader.prototype.constructor=b.PixiShader,b.PixiShader.prototype.init=function(){var a=this.gl,c=b.compileProgram(a,this.vertexSrc||b.PixiShader.defaultVertexSrc,this.fragmentSrc);a.useProgram(c),this.uSampler=a.getUniformLocation(c,\"uSampler\"),this.projectionVector=a.getUniformLocation(c,\"projectionVector\"),this.offsetVector=a.getUniformLocation(c,\"offsetVector\"),this.dimensions=a.getUniformLocation(c,\"dimensions\"),this.aVertexPosition=a.getAttribLocation(c,\"aVertexPosition\"),this.aTextureCoord=a.getAttribLocation(c,\"aTextureCoord\"),this.colorAttribute=a.getAttribLocation(c,\"aColor\"),-1===this.colorAttribute&&(this.colorAttribute=2),this.attributes=[this.aVertexPosition,this.aTextureCoord,this.colorAttribute];for(var d in this.uniforms)this.uniforms[d].uniformLocation=a.getUniformLocation(c,d);this.initUniforms(),this.program=c},b.PixiShader.prototype.initUniforms=function(){this.textureCount=1;var a,b=this.gl;for(var c in this.uniforms){a=this.uniforms[c];var d=a.type;\"sampler2D\"===d?(a._init=!1,null!==a.value&&this.initSampler2D(a)):\"mat2\"===d||\"mat3\"===d||\"mat4\"===d?(a.glMatrix=!0,a.glValueLength=1,\"mat2\"===d?a.glFunc=b.uniformMatrix2fv:\"mat3\"===d?a.glFunc=b.uniformMatrix3fv:\"mat4\"===d&&(a.glFunc=b.uniformMatrix4fv)):(a.glFunc=b[\"uniform\"+d],a.glValueLength=\"2f\"===d||\"2i\"===d?2:\"3f\"===d||\"3i\"===d?3:\"4f\"===d||\"4i\"===d?4:1)}},b.PixiShader.prototype.initSampler2D=function(a){if(a.value&&a.value.baseTexture&&a.value.baseTexture.hasLoaded){var b=this.gl;if(b.activeTexture(b[\"TEXTURE\"+this.textureCount]),b.bindTexture(b.TEXTURE_2D,a.value.baseTexture._glTextures[b.id]),a.textureData){var c=a.textureData,d=c.magFilter?c.magFilter:b.LINEAR,e=c.minFilter?c.minFilter:b.LINEAR,f=c.wrapS?c.wrapS:b.CLAMP_TO_EDGE,g=c.wrapT?c.wrapT:b.CLAMP_TO_EDGE,h=c.luminance?b.LUMINANCE:b.RGBA;if(c.repeat&&(f=b.REPEAT,g=b.REPEAT),b.pixelStorei(b.UNPACK_FLIP_Y_WEBGL,!!c.flipY),c.width){var i=c.width?c.width:512,j=c.height?c.height:2,k=c.border?c.border:0;b.texImage2D(b.TEXTURE_2D,0,h,i,j,k,h,b.UNSIGNED_BYTE,null)}else b.texImage2D(b.TEXTURE_2D,0,h,b.RGBA,b.UNSIGNED_BYTE,a.value.baseTexture.source);b.texParameteri(b.TEXTURE_2D,b.TEXTURE_MAG_FILTER,d),b.texParameteri(b.TEXTURE_2D,b.TEXTURE_MIN_FILTER,e),b.texParameteri(b.TEXTURE_2D,b.TEXTURE_WRAP_S,f),b.texParameteri(b.TEXTURE_2D,b.TEXTURE_WRAP_T,g)}b.uniform1i(a.uniformLocation,this.textureCount),a._init=!0,this.textureCount++}},b.PixiShader.prototype.syncUniforms=function(){this.textureCount=1;var a,c=this.gl;for(var d in this.uniforms)a=this.uniforms[d],1===a.glValueLength?a.glMatrix===!0?a.glFunc.call(c,a.uniformLocation,a.transpose,a.value):a.glFunc.call(c,a.uniformLocation,a.value):2===a.glValueLength?a.glFunc.call(c,a.uniformLocation,a.value.x,a.value.y):3===a.glValueLength?a.glFunc.call(c,a.uniformLocation,a.value.x,a.value.y,a.value.z):4===a.glValueLength?a.glFunc.call(c,a.uniformLocation,a.value.x,a.value.y,a.value.z,a.value.w):\"sampler2D\"===a.type&&(a._init?(c.activeTexture(c[\"TEXTURE\"+this.textureCount]),a.value.baseTexture._dirty[c.id]?b.instances[c.id].updateTexture(a.value.baseTexture):c.bindTexture(c.TEXTURE_2D,a.value.baseTexture._glTextures[c.id]),c.uniform1i(a.uniformLocation,this.textureCount),this.textureCount++):this.initSampler2D(a))},b.PixiShader.prototype.destroy=function(){this.gl.deleteProgram(this.program),this.uniforms=null,this.gl=null,this.attributes=null},b.PixiShader.defaultVertexSrc=[\"attribute vec2 aVertexPosition;\",\"attribute vec2 aTextureCoord;\",\"attribute vec4 aColor;\",\"uniform vec2 projectionVector;\",\"uniform vec2 offsetVector;\",\"varying vec2 vTextureCoord;\",\"varying vec4 vColor;\",\"const vec2 center = vec2(-1.0, 1.0);\",\"void main(void) {\",\" gl_Position = vec4( ((aVertexPosition + offsetVector) / projectionVector) + center , 0.0, 1.0);\",\" vTextureCoord = aTextureCoord;\",\" vColor = vec4(aColor.rgb * aColor.a, aColor.a);\",\"}\"],b.PixiFastShader=function(a){this._UID=b._UID++,this.gl=a,this.program=null,this.fragmentSrc=[\"precision lowp float;\",\"varying vec2 vTextureCoord;\",\"varying float vColor;\",\"uniform sampler2D uSampler;\",\"void main(void) {\",\" gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;\",\"}\"],this.vertexSrc=[\"attribute vec2 aVertexPosition;\",\"attribute vec2 aPositionCoord;\",\"attribute vec2 aScale;\",\"attribute float aRotation;\",\"attribute vec2 aTextureCoord;\",\"attribute float aColor;\",\"uniform vec2 projectionVector;\",\"uniform vec2 offsetVector;\",\"uniform mat3 uMatrix;\",\"varying vec2 vTextureCoord;\",\"varying float vColor;\",\"const vec2 center = vec2(-1.0, 1.0);\",\"void main(void) {\",\" vec2 v;\",\" vec2 sv = aVertexPosition * aScale;\",\" v.x = (sv.x) * cos(aRotation) - (sv.y) * sin(aRotation);\",\" v.y = (sv.x) * sin(aRotation) + (sv.y) * cos(aRotation);\",\" v = ( uMatrix * vec3(v + aPositionCoord , 1.0) ).xy ;\",\" gl_Position = vec4( ( v / projectionVector) + center , 0.0, 1.0);\",\" vTextureCoord = aTextureCoord;\",\" vColor = aColor;\",\"}\"],this.textureCount=0,this.init()},b.PixiFastShader.prototype.constructor=b.PixiFastShader,b.PixiFastShader.prototype.init=function(){var a=this.gl,c=b.compileProgram(a,this.vertexSrc,this.fragmentSrc);a.useProgram(c),this.uSampler=a.getUniformLocation(c,\"uSampler\"),this.projectionVector=a.getUniformLocation(c,\"projectionVector\"),this.offsetVector=a.getUniformLocation(c,\"offsetVector\"),this.dimensions=a.getUniformLocation(c,\"dimensions\"),this.uMatrix=a.getUniformLocation(c,\"uMatrix\"),this.aVertexPosition=a.getAttribLocation(c,\"aVertexPosition\"),this.aPositionCoord=a.getAttribLocation(c,\"aPositionCoord\"),this.aScale=a.getAttribLocation(c,\"aScale\"),this.aRotation=a.getAttribLocation(c,\"aRotation\"),this.aTextureCoord=a.getAttribLocation(c,\"aTextureCoord\"),this.colorAttribute=a.getAttribLocation(c,\"aColor\"),-1===this.colorAttribute&&(this.colorAttribute=2),this.attributes=[this.aVertexPosition,this.aPositionCoord,this.aScale,this.aRotation,this.aTextureCoord,this.colorAttribute],this.program=c},b.PixiFastShader.prototype.destroy=function(){this.gl.deleteProgram(this.program),this.uniforms=null,this.gl=null,this.attributes=null},b.StripShader=function(a){this._UID=b._UID++,this.gl=a,this.program=null,this.fragmentSrc=[\"precision mediump float;\",\"varying vec2 vTextureCoord;\",\"uniform float alpha;\",\"uniform sampler2D uSampler;\",\"void main(void) {\",\" gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y)) * alpha;\",\"}\"],this.vertexSrc=[\"attribute vec2 aVertexPosition;\",\"attribute vec2 aTextureCoord;\",\"uniform mat3 translationMatrix;\",\"uniform vec2 projectionVector;\",\"uniform vec2 offsetVector;\",\"varying vec2 vTextureCoord;\",\"void main(void) {\",\" vec3 v = translationMatrix * vec3(aVertexPosition , 1.0);\",\" v -= offsetVector.xyx;\",\" gl_Position = vec4( v.x / projectionVector.x -1.0, v.y / -projectionVector.y + 1.0 , 0.0, 1.0);\",\" vTextureCoord = aTextureCoord;\",\"}\"],this.init()},b.StripShader.prototype.constructor=b.StripShader,b.StripShader.prototype.init=function(){var a=this.gl,c=b.compileProgram(a,this.vertexSrc,this.fragmentSrc);a.useProgram(c),this.uSampler=a.getUniformLocation(c,\"uSampler\"),this.projectionVector=a.getUniformLocation(c,\"projectionVector\"),this.offsetVector=a.getUniformLocation(c,\"offsetVector\"),this.colorAttribute=a.getAttribLocation(c,\"aColor\"),this.aVertexPosition=a.getAttribLocation(c,\"aVertexPosition\"),this.aTextureCoord=a.getAttribLocation(c,\"aTextureCoord\"),this.attributes=[this.aVertexPosition,this.aTextureCoord],this.translationMatrix=a.getUniformLocation(c,\"translationMatrix\"),this.alpha=a.getUniformLocation(c,\"alpha\"),this.program=c},b.StripShader.prototype.destroy=function(){this.gl.deleteProgram(this.program),this.uniforms=null,this.gl=null,this.attribute=null},b.PrimitiveShader=function(a){this._UID=b._UID++,this.gl=a,this.program=null,this.fragmentSrc=[\"precision mediump float;\",\"varying vec4 vColor;\",\"void main(void) {\",\" gl_FragColor = vColor;\",\"}\"],this.vertexSrc=[\"attribute vec2 aVertexPosition;\",\"attribute vec4 aColor;\",\"uniform mat3 translationMatrix;\",\"uniform vec2 projectionVector;\",\"uniform vec2 offsetVector;\",\"uniform float alpha;\",\"uniform float flipY;\",\"uniform vec3 tint;\",\"varying vec4 vColor;\",\"void main(void) {\",\" vec3 v = translationMatrix * vec3(aVertexPosition , 1.0);\",\" v -= offsetVector.xyx;\",\" gl_Position = vec4( v.x / projectionVector.x -1.0, (v.y / projectionVector.y * -flipY) + flipY , 0.0, 1.0);\",\" vColor = aColor * vec4(tint * alpha, alpha);\",\"}\"],this.init()},b.PrimitiveShader.prototype.constructor=b.PrimitiveShader,b.PrimitiveShader.prototype.init=function(){var a=this.gl,c=b.compileProgram(a,this.vertexSrc,this.fragmentSrc);a.useProgram(c),this.projectionVector=a.getUniformLocation(c,\"projectionVector\"),this.offsetVector=a.getUniformLocation(c,\"offsetVector\"),this.tintColor=a.getUniformLocation(c,\"tint\"),this.flipY=a.getUniformLocation(c,\"flipY\"),this.aVertexPosition=a.getAttribLocation(c,\"aVertexPosition\"),this.colorAttribute=a.getAttribLocation(c,\"aColor\"),this.attributes=[this.aVertexPosition,this.colorAttribute],this.translationMatrix=a.getUniformLocation(c,\"translationMatrix\"),this.alpha=a.getUniformLocation(c,\"alpha\"),this.program=c},b.PrimitiveShader.prototype.destroy=function(){this.gl.deleteProgram(this.program),this.uniforms=null,this.gl=null,this.attributes=null},b.ComplexPrimitiveShader=function(a){this._UID=b._UID++,this.gl=a,this.program=null,this.fragmentSrc=[\"precision mediump float;\",\"varying vec4 vColor;\",\"void main(void) {\",\" gl_FragColor = vColor;\",\"}\"],this.vertexSrc=[\"attribute vec2 aVertexPosition;\",\"uniform mat3 translationMatrix;\",\"uniform vec2 projectionVector;\",\"uniform vec2 offsetVector;\",\"uniform vec3 tint;\",\"uniform float alpha;\",\"uniform vec3 color;\",\"uniform float flipY;\",\"varying vec4 vColor;\",\"void main(void) {\",\" vec3 v = translationMatrix * vec3(aVertexPosition , 1.0);\",\" v -= offsetVector.xyx;\",\" gl_Position = vec4( v.x / projectionVector.x -1.0, (v.y / projectionVector.y * -flipY) + flipY , 0.0, 1.0);\",\" vColor = vec4(color * alpha * tint, alpha);\",\"}\"],this.init()},b.ComplexPrimitiveShader.prototype.constructor=b.ComplexPrimitiveShader,b.ComplexPrimitiveShader.prototype.init=function(){var a=this.gl,c=b.compileProgram(a,this.vertexSrc,this.fragmentSrc);a.useProgram(c),this.projectionVector=a.getUniformLocation(c,\"projectionVector\"),this.offsetVector=a.getUniformLocation(c,\"offsetVector\"),this.tintColor=a.getUniformLocation(c,\"tint\"),this.color=a.getUniformLocation(c,\"color\"),this.flipY=a.getUniformLocation(c,\"flipY\"),this.aVertexPosition=a.getAttribLocation(c,\"aVertexPosition\"),this.attributes=[this.aVertexPosition,this.colorAttribute],this.translationMatrix=a.getUniformLocation(c,\"translationMatrix\"),this.alpha=a.getUniformLocation(c,\"alpha\"),this.program=c},b.ComplexPrimitiveShader.prototype.destroy=function(){this.gl.deleteProgram(this.program),this.uniforms=null,this.gl=null,this.attribute=null},b.WebGLGraphics=function(){},b.WebGLGraphics.renderGraphics=function(a,c){var d,e=c.gl,f=c.projection,g=c.offset,h=c.shaderManager.primitiveShader;a.dirty&&b.WebGLGraphics.updateGraphics(a,e);for(var i=a._webGL[e.id],j=0;jg?g:j,k=g>k?g:k;d.push(h,j,i,j,i,k,h,k);var m=d.length/2;for(l=0;m>l;l++)e.push(l)}},b.WebGLGraphics.buildPoly=function(a,c){var d=a.points;if(!(d.length.5*this.size)a.bufferSubData(a.ARRAY_BUFFER,0,this.vertices);else{var b=this.vertices.subarray(0,4*this.currentBatchSize*this.vertSize);a.bufferSubData(a.ARRAY_BUFFER,0,b)}a.drawElements(a.TRIANGLES,6*this.currentBatchSize,a.UNSIGNED_SHORT,0),this.currentBatchSize=0,this.renderSession.drawCount++}},b.WebGLFastSpriteBatch.prototype.stop=function(){this.flush()},b.WebGLFastSpriteBatch.prototype.start=function(){var a=this.gl;a.activeTexture(a.TEXTURE0),a.bindBuffer(a.ARRAY_BUFFER,this.vertexBuffer),a.bindBuffer(a.ELEMENT_ARRAY_BUFFER,this.indexBuffer);var b=this.renderSession.projection;a.uniform2f(this.shader.projectionVector,b.x,b.y),a.uniformMatrix3fv(this.shader.uMatrix,!1,this.matrix);var c=4*this.vertSize;a.vertexAttribPointer(this.shader.aVertexPosition,2,a.FLOAT,!1,c,0),a.vertexAttribPointer(this.shader.aPositionCoord,2,a.FLOAT,!1,c,8),a.vertexAttribPointer(this.shader.aScale,2,a.FLOAT,!1,c,16),a.vertexAttribPointer(this.shader.aRotation,1,a.FLOAT,!1,c,24),a.vertexAttribPointer(this.shader.aTextureCoord,2,a.FLOAT,!1,c,28),a.vertexAttribPointer(this.shader.colorAttribute,1,a.FLOAT,!1,c,36)},b.WebGLFilterManager=function(){this.filterStack=[],this.offsetX=0,this.offsetY=0},b.WebGLFilterManager.prototype.constructor=b.WebGLFilterManager,b.WebGLFilterManager.prototype.setContext=function(a){this.gl=a,this.texturePool=[],this.initShaderBuffers()},b.WebGLFilterManager.prototype.begin=function(a,b){this.renderSession=a,this.defaultShader=a.shaderManager.defaultShader;var c=this.renderSession.projection;this.width=2*c.x,this.height=2*-c.y,this.buffer=b},b.WebGLFilterManager.prototype.pushFilter=function(a){var c=this.gl,d=this.renderSession.projection,e=this.renderSession.offset;a._filterArea=a.target.filterArea||a.target.getBounds(),this.filterStack.push(a);var f=a.filterPasses[0];this.offsetX+=a._filterArea.x,this.offsetY+=a._filterArea.y;var g=this.texturePool.pop();g?g.resize(this.width,this.height):g=new b.FilterTexture(this.gl,this.width,this.height),c.bindTexture(c.TEXTURE_2D,g.texture);var h=a._filterArea,i=f.padding;h.x-=i,h.y-=i,h.width+=2*i,h.height+=2*i,h.xthis.width&&(h.width=this.width),h.ythis.height&&(h.height=this.height),c.bindFramebuffer(c.FRAMEBUFFER,g.frameBuffer),c.viewport(0,0,h.width,h.height),d.x=h.width/2,d.y=-h.height/2,e.x=-h.x,e.y=-h.y,c.colorMask(!0,!0,!0,!0),c.clearColor(0,0,0,0),c.clear(c.COLOR_BUFFER_BIT),a._glFilterTexture=g},b.WebGLFilterManager.prototype.popFilter=function(){var a=this.gl,c=this.filterStack.pop(),d=c._filterArea,e=c._glFilterTexture,f=this.renderSession.projection,g=this.renderSession.offset;if(c.filterPasses.length>1){a.viewport(0,0,d.width,d.height),a.bindBuffer(a.ARRAY_BUFFER,this.vertexBuffer),this.vertexArray[0]=0,this.vertexArray[1]=d.height,this.vertexArray[2]=d.width,this.vertexArray[3]=d.height,this.vertexArray[4]=0,this.vertexArray[5]=0,this.vertexArray[6]=d.width,this.vertexArray[7]=0,a.bufferSubData(a.ARRAY_BUFFER,0,this.vertexArray),a.bindBuffer(a.ARRAY_BUFFER,this.uvBuffer),this.uvArray[2]=d.width/this.width,this.uvArray[5]=d.height/this.height,this.uvArray[6]=d.width/this.width,this.uvArray[7]=d.height/this.height,a.bufferSubData(a.ARRAY_BUFFER,0,this.uvArray);var h=e,i=this.texturePool.pop();i||(i=new b.FilterTexture(this.gl,this.width,this.height)),i.resize(this.width,this.height),a.bindFramebuffer(a.FRAMEBUFFER,i.frameBuffer),a.clear(a.COLOR_BUFFER_BIT),a.disable(a.BLEND);for(var j=0;j16&255)/255,c=(a.tint>>8&255)/255,d=(255&a.tint)/255,e=0;e>16&255)/255*b*2558&255)/255*c*25516&255)/255*b*2558&255)/255*c*255m||0===e)(f[f.length-2]!==a||f[f.length-1]!==b)&&f.push(a,b);else{var n=i*i+j*j,o=k*k+l*l,p=i*k+j*l,q=e*Math.sqrt(n)/m,r=e*Math.sqrt(o)/m,s=q*p/n,t=r*p/o,u=q*l+r*j,v=q*k+r*i,w=j*(r+s),x=i*(r+s),y=l*(q+t),z=k*(q+t),A=Math.atan2(x-v,w-u),B=Math.atan2(z-v,y-u);this.arc(u+a,v+b,e,A,B,j*k>l*i)}return this.dirty=!0,this},b.Graphics.prototype.arc=function(a,b,c,d,e,f){var g,h=a+Math.cos(d)*c,i=b+Math.sin(d)*c;if(this.currentPath?(g=this.currentPath.shape.points,0===g.length?g.push(h,i):(g[g.length-2]!==h||g[g.length-1]!==i)&&g.push(h,i)):(this.moveTo(h,i),g=this.currentPath.shape.points),d===e)return this;!f&&d>=e?e+=2*Math.PI:f&&e>=d&&(d+=2*Math.PI);var j=f?-1*(d-e):e-d,k=Math.abs(j)/(2*Math.PI)*40;if(0===j)return this;for(var l=j/(2*k),m=2*l,n=Math.cos(l),o=Math.sin(l),p=k-1,q=p%1/p,r=0;p>=r;r++){var s=r+q*r,t=l+d+m*s,u=Math.cos(t),v=-Math.sin(t);g.push((n*u+o*v)*c+a,(n*-v+o*u)*c+b)}return this.dirty=!0,this},b.Graphics.prototype.beginFill=function(a,b){return this.filling=!0,this.fillColor=a||0,this.fillAlpha=void 0===b?1:b,this.currentPath&&this.currentPath.shape.points.lengthd;d++)this.children[d]._renderWebGL(a);a.spriteBatch.stop()}this._filters&&a.filterManager.popFilter(),this._mask&&a.maskManager.popMask(this.mask,a),a.drawCount++,a.spriteBatch.start()}},b.Graphics.prototype._renderCanvas=function(a){if(this.visible!==!1&&0!==this.alpha&&this.isMask!==!0){if(this._cacheAsBitmap)return(this.dirty||this.cachedSpriteDirty)&&(this._generateCachedSprite(),this.updateCachedSpriteTexture(),this.cachedSpriteDirty=!1,this.dirty=!1),this._cachedSprite.alpha=this.alpha,void b.Sprite.prototype._renderCanvas.call(this._cachedSprite,a);var c=a.context,d=this.worldTransform;this.blendMode!==a.currentBlendMode&&(a.currentBlendMode=this.blendMode,c.globalCompositeOperation=b.blendModesCanvas[a.currentBlendMode]),this._mask&&a.maskManager.pushMask(this._mask,a);var e=a.resolution;c.setTransform(d.a*e,d.b*e,d.c*e,d.d*e,d.tx*e,d.ty*e),b.CanvasGraphics.renderGraphics(this,c);for(var f=0,g=this.children.length;g>f;f++)this.children[f]._renderCanvas(a);this._mask&&a.maskManager.popMask(a)}},b.Graphics.prototype.getBounds=function(a){if(this.isMask)return b.EmptyRectangle;this.dirty&&(this.updateLocalBounds(),this.webGLDirty=!0,this.cachedSpriteDirty=!0,this.dirty=!1);var c=this._localBounds,d=c.x,e=c.width+c.x,f=c.y,g=c.height+c.y,h=a||this.worldTransform,i=h.a,j=h.b,k=h.c,l=h.d,m=h.tx,n=h.ty,o=i*e+k*g+m,p=l*g+j*e+n,q=i*d+k*g+m,r=l*g+j*d+n,s=i*d+k*f+m,t=l*f+j*d+n,u=i*e+k*f+m,v=l*f+j*e+n,w=o,x=p,y=o,z=p;return y=y>q?q:y,y=y>s?s:y,y=y>u?u:y,z=z>r?r:z,z=z>t?t:z,z=z>v?v:z,w=q>w?q:w,w=s>w?s:w,w=u>w?u:w,x=r>x?r:x,x=t>x?t:x,x=v>x?v:x,this._bounds.x=y,this._bounds.width=w-y,this._bounds.y=z,this._bounds.height=x-z,this._bounds},b.Graphics.prototype.updateLocalBounds=function(){var a=1/0,c=-1/0,d=1/0,e=-1/0;if(this.graphicsData.length)for(var f,g,h,i,j,k,l=0;lh?h:a,c=h+j>c?h+j:c,d=d>i?i:d,e=i+k>e?i+k:e;else if(n===b.Graphics.CIRC)h=f.x,i=f.y,j=f.radius+o/2,k=f.radius+o/2,a=a>h-j?h-j:a,c=h+j>c?h+j:c,d=d>i-k?i-k:d,e=i+k>e?i+k:e;else if(n===b.Graphics.ELIP)h=f.x,i=f.y,j=f.width+o/2,k=f.height+o/2,a=a>h-j?h-j:a,c=h+j>c?h+j:c,d=d>i-k?i-k:d,e=i+k>e?i+k:e;else{g=f.points;for(var p=0;ph-o?h-o:a,c=h+o>c?h+o:c,d=d>i-o?i-o:d,e=i+o>e?i+o:e}}else a=0,c=0,d=0,e=0;var q=this.boundsPadding;this._localBounds.x=a-q,this._localBounds.width=c-a+2*q,this._localBounds.y=d-q,this._localBounds.height=e-d+2*q},b.Graphics.prototype._generateCachedSprite=function(){var a=this.getLocalBounds();if(this._cachedSprite)this._cachedSprite.buffer.resize(a.width,a.height);else{var c=new b.CanvasBuffer(a.width,a.height),d=b.Texture.fromCanvas(c.canvas);this._cachedSprite=new b.Sprite(d),this._cachedSprite.buffer=c,this._cachedSprite.worldTransform=this.worldTransform}this._cachedSprite.anchor.x=-(a.x/a.width),this._cachedSprite.anchor.y=-(a.y/a.height),this._cachedSprite.buffer.context.translate(-a.x,-a.y),this.worldAlpha=1,b.CanvasGraphics.renderGraphics(this,this._cachedSprite.buffer.context),this._cachedSprite.alpha=this.alpha},b.Graphics.prototype.updateCachedSpriteTexture=function(){var a=this._cachedSprite,b=a.texture,c=a.buffer.canvas;b.baseTexture.width=c.width,b.baseTexture.height=c.height,b.crop.width=b.frame.width=c.width,b.crop.height=b.frame.height=c.height,a._width=c.width,a._height=c.height,b.baseTexture.dirty()},b.Graphics.prototype.destroyCachedSprite=function(){this._cachedSprite.texture.destroy(!0),this._cachedSprite=null},b.Graphics.prototype.drawShape=function(a){this.currentPath&&this.currentPath.shape.points.lengthf;f+=3){var g=2*d[f],h=2*d[f+1],i=2*d[f+2];this._renderCanvasDrawTriangle(a,b,c,g,h,i)}},b.Strip.prototype._renderCanvasDrawTriangle=function(a,b,c,d,e,f){var g=this.texture.baseTexture.source,h=this.texture.width,i=this.texture.height,j=b[d],k=b[e],l=b[f],m=b[d+1],n=b[e+1],o=b[f+1],p=c[d]*h,q=c[e]*h,r=c[f]*h,s=c[d+1]*i,t=c[e+1]*i,u=c[f+1]*i;if(this.canvasPadding>0){var v=this.canvasPadding/this.worldTransform.a,w=this.canvasPadding/this.worldTransform.d,x=(j+k+l)/3,y=(m+n+o)/3,z=j-x,A=m-y,B=Math.sqrt(z*z+A*A);j=x+z/B*(B+v),m=y+A/B*(B+w),z=k-x,A=n-y,B=Math.sqrt(z*z+A*A),k=x+z/B*(B+v),n=y+A/B*(B+w),z=l-x,A=o-y,B=Math.sqrt(z*z+A*A),l=x+z/B*(B+v),o=y+A/B*(B+w)}a.save(),a.beginPath(),a.moveTo(j,m),a.lineTo(k,n),a.lineTo(l,o),a.closePath(),a.clip();var C=p*t+s*r+q*u-t*r-s*q-p*u,D=j*t+s*l+k*u-t*l-s*k-j*u,E=p*k+j*r+q*l-k*r-j*q-p*l,F=p*t*l+s*k*r+j*q*u-j*t*r-s*q*l-p*k*u,G=m*t+s*o+n*u-t*o-s*n-m*u,H=p*n+m*r+q*o-n*r-m*q-p*o,I=p*t*o+s*n*r+m*q*u-m*t*r-s*q*o-p*n*u;a.transform(D/C,G/C,E/C,H/C,F/C,I/C),a.drawImage(g,0,0),a.restore()},b.Strip.prototype.renderStripFlat=function(a){var b=this.context,c=a.vertices,d=c.length/2;this.count++,b.beginPath();for(var e=1;d-2>e;e++){var f=2*e,g=c[f],h=c[f+2],i=c[f+4],j=c[f+1],k=c[f+3],l=c[f+5];b.moveTo(g,j),b.lineTo(h,k),b.lineTo(i,l)}b.fillStyle=\"#FF0000\",b.fill(),b.closePath()},b.Strip.prototype.onTextureUpdate=function(){this.updateFrame=!0},b.Strip.prototype.getBounds=function(a){for(var c=a||this.worldTransform,d=c.a,e=c.b,f=c.c,g=c.d,h=c.tx,i=c.ty,j=-1/0,k=-1/0,l=1/0,m=1/0,n=this.vertices,o=0,p=n.length;p>o;o+=2){var q=n[o],r=n[o+1],s=d*q+f*r+h,t=g*r+e*q+i;l=l>s?s:l,m=m>t?t:m,j=s>j?s:j,k=t>k?t:k}if(l===-1/0||1/0===k)return b.EmptyRectangle;var u=this._bounds;return u.x=l,u.width=j-l,u.y=m,u.height=k-m,this._currentBounds=u,u},b.Strip.DrawModes={TRIANGLE_STRIP:0,TRIANGLES:1},b.Rope=function(a,c){b.Strip.call(this,a),this.points=c,this.vertices=new b.Float32Array(4*c.length),this.uvs=new b.Float32Array(4*c.length),this.colors=new b.Float32Array(2*c.length),this.indices=new b.Uint16Array(2*c.length),this.refresh()},b.Rope.prototype=Object.create(b.Strip.prototype),b.Rope.prototype.constructor=b.Rope,b.Rope.prototype.refresh=function(){var a=this.points;if(!(a.lengthj;j++)f=a[j],g=4*j,h=j/(i-1),j%2?(b[g]=h,b[g+1]=0,b[g+2]=h,b[g+3]=1):(b[g]=h,b[g+1]=0,b[g+2]=h,b[g+3]=1),g=2*j,e[g]=1,e[g+1]=1,g=2*j,d[g]=g,d[g+1]=g+1,c=f}},b.Rope.prototype.updateTransform=function(){var a=this.points;if(!(a.lengthm;m++)f=a[m],g=4*m,c=m1&&(h=1),i=Math.sqrt(e.x*e.x+e.y*e.y),j=this.texture.height/2,e.x/=i,e.y/=i,e.x*=j,e.y*=j,k[g]=f.x+e.x,k[g+1]=f.y+e.y,k[g+2]=f.x-e.x,k[g+3]=f.y-e.y,d=f;b.DisplayObjectContainer.prototype.updateTransform.call(this)}},b.Rope.prototype.setTexture=function(a){this.texture=a},b.TilingSprite=function(a,c,d){b.Sprite.call(this,a),this._width=c||100,this._height=d||100,this.tileScale=new b.Point(1,1),this.tileScaleOffset=new b.Point(1,1),this.tilePosition=new b.Point(0,0),this.renderable=!0,this.tint=16777215,this.blendMode=b.blendModes.NORMAL},b.TilingSprite.prototype=Object.create(b.Sprite.prototype),b.TilingSprite.prototype.constructor=b.TilingSprite,Object.defineProperty(b.TilingSprite.prototype,\"width\",{get:function(){return this._width},set:function(a){this._width=a}}),Object.defineProperty(b.TilingSprite.prototype,\"height\",{get:function(){return this._height},set:function(a){this._height=a}}),b.TilingSprite.prototype.setTexture=function(a){this.texture!==a&&(this.texture=a,this.refreshTexture=!0,this.cachedTint=16777215)},b.TilingSprite.prototype._renderWebGL=function(a){if(this.visible!==!1&&0!==this.alpha){var b,c;for(this._mask&&(a.spriteBatch.stop(),a.maskManager.pushMask(this.mask,a),a.spriteBatch.start()),this._filters&&(a.spriteBatch.flush(),a.filterManager.pushFilter(this._filterBlock)),!this.tilingTexture||this.refreshTexture?(this.generateTilingTexture(!0),this.tilingTexture&&this.tilingTexture.needsUpdate&&(a.renderer.updateTexture(this.tilingTexture.baseTexture),this.tilingTexture.needsUpdate=!1)):a.spriteBatch.renderTilingSprite(this),b=0,c=this.children.length;c>b;b++)this.children[b]._renderWebGL(a);a.spriteBatch.stop(),this._filters&&a.filterManager.popFilter(),this._mask&&a.maskManager.popMask(this._mask,a),a.spriteBatch.start()}},b.TilingSprite.prototype._renderCanvas=function(a){if(this.visible!==!1&&0!==this.alpha){var c=a.context;this._mask&&a.maskManager.pushMask(this._mask,c),c.globalAlpha=this.worldAlpha;var d,e,f=this.worldTransform,g=a.resolution;if(c.setTransform(f.a*g,f.b*g,f.c*g,f.d*g,f.tx*g,f.ty*g),!this.__tilePattern||this.refreshTexture){if(this.generateTilingTexture(!1),!this.tilingTexture)return;this.__tilePattern=c.createPattern(this.tilingTexture.baseTexture.source,\"repeat\")}this.blendMode!==a.currentBlendMode&&(a.currentBlendMode=this.blendMode,c.globalCompositeOperation=b.blendModesCanvas[a.currentBlendMode]);var h=this.tilePosition,i=this.tileScale;for(h.x%=this.tilingTexture.baseTexture.width,h.y%=this.tilingTexture.baseTexture.height,c.scale(i.x,i.y),c.translate(h.x+this.anchor.x*-this._width,h.y+this.anchor.y*-this._height),c.fillStyle=this.__tilePattern,c.fillRect(-h.x,-h.y,this._width/i.x,this._height/i.y),c.scale(1/i.x,1/i.y),c.translate(-h.x+this.anchor.x*this._width,-h.y+this.anchor.y*this._height),this._mask&&a.maskManager.popMask(a.context),d=0,e=this.children.length;e>d;d++)this.children[d]._renderCanvas(a) }},b.TilingSprite.prototype.getBounds=function(){var a=this._width,b=this._height,c=a*(1-this.anchor.x),d=a*-this.anchor.x,e=b*(1-this.anchor.y),f=b*-this.anchor.y,g=this.worldTransform,h=g.a,i=g.b,j=g.c,k=g.d,l=g.tx,m=g.ty,n=h*d+j*f+l,o=k*f+i*d+m,p=h*c+j*f+l,q=k*f+i*c+m,r=h*c+j*e+l,s=k*e+i*c+m,t=h*d+j*e+l,u=k*e+i*d+m,v=-1/0,w=-1/0,x=1/0,y=1/0;x=x>n?n:x,x=x>p?p:x,x=x>r?r:x,x=x>t?t:x,y=y>o?o:y,y=y>q?q:y,y=y>s?s:y,y=y>u?u:y,v=n>v?n:v,v=p>v?p:v,v=r>v?r:v,v=t>v?t:v,w=o>w?o:w,w=q>w?q:w,w=s>w?s:w,w=u>w?u:w;var z=this._bounds;return z.x=x,z.width=v-x,z.y=y,z.height=w-y,this._currentBounds=z,z},b.TilingSprite.prototype.onTextureUpdate=function(){},b.TilingSprite.prototype.generateTilingTexture=function(a){if(this.texture.baseTexture.hasLoaded){var c,d,e=this.originalTexture||this.texture,f=e.frame,g=f.width!==e.baseTexture.width||f.height!==e.baseTexture.height,h=!1;if(a?(c=b.getNextPowerOfTwo(f.width),d=b.getNextPowerOfTwo(f.height),(f.width!==c||f.height!==d||e.baseTexture.width!==c||e.baseTexture.height||d)&&(h=!0)):g&&(c=f.width,d=f.height,h=!0),h){var i;this.tilingTexture&&this.tilingTexture.isTiling?(i=this.tilingTexture.canvasBuffer,i.resize(c,d),this.tilingTexture.baseTexture.width=c,this.tilingTexture.baseTexture.height=d,this.tilingTexture.needsUpdate=!0):(i=new b.CanvasBuffer(c,d),this.tilingTexture=b.Texture.fromCanvas(i.canvas),this.tilingTexture.canvasBuffer=i,this.tilingTexture.isTiling=!0),i.context.drawImage(e.baseTexture.source,e.crop.x,e.crop.y,e.crop.width,e.crop.height,0,0,c,d),this.tileScaleOffset.x=f.width/c,this.tileScaleOffset.y=f.height/d}else this.tilingTexture&&this.tilingTexture.isTiling&&this.tilingTexture.destroy(!0),this.tileScaleOffset.x=1,this.tileScaleOffset.y=1,this.tilingTexture=e;this.refreshTexture=!1,this.originalTexture=this.texture,this.texture=this.tilingTexture,this.tilingTexture.baseTexture._powerOf2=!0}},b.TilingSprite.prototype.destroy=function(){b.Sprite.prototype.destroy.call(this),this.tileScale=null,this.tileScaleOffset=null,this.tilePosition=null,this.tilingTexture.destroy(!0),this.tilingTexture=null};var c={radDeg:180/Math.PI,degRad:Math.PI/180,temp:[],Float32Array:\"undefined\"==typeof Float32Array?Array:Float32Array,Uint16Array:\"undefined\"==typeof Uint16Array?Array:Uint16Array};c.BoneData=function(a,b){this.name=a,this.parent=b},c.BoneData.prototype={length:0,x:0,y:0,rotation:0,scaleX:1,scaleY:1,inheritScale:!0,inheritRotation:!0,flipX:!1,flipY:!1},c.SlotData=function(a,b){this.name=a,this.boneData=b},c.SlotData.prototype={r:1,g:1,b:1,a:1,attachmentName:null,additiveBlending:!1},c.IkConstraintData=function(a){this.name=a,this.bones=[]},c.IkConstraintData.prototype={target:null,bendDirection:1,mix:1},c.Bone=function(a,b,c){this.data=a,this.skeleton=b,this.parent=c,this.setToSetupPose()},c.Bone.yDown=!1,c.Bone.prototype={x:0,y:0,rotation:0,rotationIK:0,scaleX:1,scaleY:1,flipX:!1,flipY:!1,m00:0,m01:0,worldX:0,m10:0,m11:0,worldY:0,worldRotation:0,worldScaleX:1,worldScaleY:1,worldFlipX:!1,worldFlipY:!1,updateWorldTransform:function(){var a=this.parent;if(a)this.worldX=this.x*a.m00+this.y*a.m01+a.worldX,this.worldY=this.x*a.m10+this.y*a.m11+a.worldY,this.data.inheritScale?(this.worldScaleX=a.worldScaleX*this.scaleX,this.worldScaleY=a.worldScaleY*this.scaleY):(this.worldScaleX=this.scaleX,this.worldScaleY=this.scaleY),this.worldRotation=this.data.inheritRotation?a.worldRotation+this.rotationIK:this.rotationIK,this.worldFlipX=a.worldFlipX!=this.flipX,this.worldFlipY=a.worldFlipY!=this.flipY;else{var b=this.skeleton.flipX,d=this.skeleton.flipY;this.worldX=b?-this.x:this.x,this.worldY=d!=c.Bone.yDown?-this.y:this.y,this.worldScaleX=this.scaleX,this.worldScaleY=this.scaleY,this.worldRotation=this.rotationIK,this.worldFlipX=b!=this.flipX,this.worldFlipY=d!=this.flipY}var e=this.worldRotation*c.degRad,f=Math.cos(e),g=Math.sin(e);this.worldFlipX?(this.m00=-f*this.worldScaleX,this.m01=g*this.worldScaleY):(this.m00=f*this.worldScaleX,this.m01=-g*this.worldScaleY),this.worldFlipY!=c.Bone.yDown?(this.m10=-g*this.worldScaleX,this.m11=-f*this.worldScaleY):(this.m10=g*this.worldScaleX,this.m11=f*this.worldScaleY)},setToSetupPose:function(){var a=this.data;this.x=a.x,this.y=a.y,this.rotation=a.rotation,this.rotationIK=this.rotation,this.scaleX=a.scaleX,this.scaleY=a.scaleY,this.flipX=a.flipX,this.flipY=a.flipY},worldToLocal:function(a){var b=a[0]-this.worldX,d=a[1]-this.worldY,e=this.m00,f=this.m10,g=this.m01,h=this.m11;this.worldFlipX!=(this.worldFlipY!=c.Bone.yDown)&&(e=-e,h=-h);var i=1/(e*h-g*f);a[0]=b*e*i-d*g*i,a[1]=d*h*i-b*f*i},localToWorld:function(a){var b=a[0],c=a[1];a[0]=b*this.m00+c*this.m01+this.worldX,a[1]=b*this.m10+c*this.m11+this.worldY}},c.Slot=function(a,b){this.data=a,this.bone=b,this.setToSetupPose()},c.Slot.prototype={r:1,g:1,b:1,a:1,_attachmentTime:0,attachment:null,attachmentVertices:[],setAttachment:function(a){this.attachment=a,this._attachmentTime=this.bone.skeleton.time,this.attachmentVertices.length=0},setAttachmentTime:function(a){this._attachmentTime=this.bone.skeleton.time-a},getAttachmentTime:function(){return this.bone.skeleton.time-this._attachmentTime},setToSetupPose:function(){var a=this.data;this.r=a.r,this.g=a.g,this.b=a.b,this.a=a.a;for(var b=this.bone.skeleton.data.slots,c=0,d=b.length;d>c;c++)if(b[c]==a){this.setAttachment(a.attachmentName?this.bone.skeleton.getAttachmentBySlotIndex(c,a.attachmentName):null);break}}},c.IkConstraint=function(a,b){this.data=a,this.mix=a.mix,this.bendDirection=a.bendDirection,this.bones=[];for(var c=0,d=a.bones.length;d>c;c++)this.bones.push(b.findBone(a.bones[c].name));this.target=b.findBone(a.target.name)},c.IkConstraint.prototype={apply:function(){var a=this.target,b=this.bones;switch(b.length){case 1:c.IkConstraint.apply1(b[0],a.worldX,a.worldY,this.mix);break;case 2:c.IkConstraint.apply2(b[0],b[1],a.worldX,a.worldY,this.bendDirection,this.mix)}}},c.IkConstraint.apply1=function(a,b,d,e){var f=a.data.inheritRotation&&a.parent?a.parent.worldRotation:0,g=a.rotation,h=Math.atan2(d-a.worldY,b-a.worldX)*c.radDeg-f;a.rotationIK=g+(h-g)*e},c.IkConstraint.apply2=function(a,b,d,e,f,g){var h=b.rotation,i=a.rotation;if(!g)return b.rotationIK=h,void(a.rotationIK=i);var j,k,l=c.temp,m=a.parent;m?(l[0]=d,l[1]=e,m.worldToLocal(l),d=(l[0]-a.x)*m.worldScaleX,e=(l[1]-a.y)*m.worldScaleY):(d-=a.x,e-=a.y),b.parent==a?(j=b.x,k=b.y):(l[0]=b.x,l[1]=b.y,b.parent.localToWorld(l),a.worldToLocal(l),j=l[0],k=l[1]);var n=j*a.worldScaleX,o=k*a.worldScaleY,p=Math.atan2(o,n),q=Math.sqrt(n*n+o*o),r=b.data.length*b.worldScaleX,s=2*q*r;if(1e-4>s)return void(b.rotationIK=h+(Math.atan2(e,d)*c.radDeg-i-h)*g);var t=(d*d+e*e-q*q-r*r)/s;-1>t?t=-1:t>1&&(t=1);var u=Math.acos(t)*f,v=q+r*t,w=r*Math.sin(u),x=Math.atan2(e*v-d*w,d*v+e*w),y=(x-p)*c.radDeg-i;y>180?y-=360:-180>y&&(y+=360),a.rotationIK=i+y*g,y=(u+p)*c.radDeg-h,y>180?y-=360:-180>y&&(y+=360),b.rotationIK=h+(y+a.worldRotation-b.parent.worldRotation)*g},c.Skin=function(a){this.name=a,this.attachments={}},c.Skin.prototype={addAttachment:function(a,b,c){this.attachments[a+\":\"+b]=c},getAttachment:function(a,b){return this.attachments[a+\":\"+b]},_attachAll:function(a,b){for(var c in b.attachments){var d=c.indexOf(\":\"),e=parseInt(c.substring(0,d)),f=c.substring(d+1),g=a.slots[e];if(g.attachment&&g.attachment.name==f){var h=this.getAttachment(e,f);h&&g.setAttachment(h)}}}},c.Animation=function(a,b,c){this.name=a,this.timelines=b,this.duration=c},c.Animation.prototype={apply:function(a,b,c,d,e){d&&0!=this.duration&&(c%=this.duration,b%=this.duration);for(var f=this.timelines,g=0,h=f.length;h>g;g++)f[g].apply(a,b,c,e,1)},mix:function(a,b,c,d,e,f){d&&0!=this.duration&&(c%=this.duration,b%=this.duration);for(var g=this.timelines,h=0,i=g.length;i>h;h++)g[h].apply(a,b,c,e,f)}},c.Animation.binarySearch=function(a,b,c){var d=0,e=Math.floor(a.length/c)-2;if(!e)return c;for(var f=e>>>1;;){if(a[(f+1)*c]>>1}},c.Animation.binarySearch1=function(a,b){var c=0,d=a.length-2;if(!d)return 1;for(var e=d>>>1;;){if(a[e+1]>>1}},c.Animation.linearSearch=function(a,b,c){for(var d=0,e=a.length-c;e>=d;d+=c)if(a[d]>b)return d;return-1},c.Curves=function(){this.curves=[]},c.Curves.prototype={setLinear:function(a){this.curves[19*a]=0},setStepped:function(a){this.curves[19*a]=1},setCurve:function(a,b,c,d,e){var f=.1,g=f*f,h=g*f,i=3*f,j=3*g,k=6*g,l=6*h,m=2*-b+d,n=2*-c+e,o=3*(b-d)+1,p=3*(c-e)+1,q=b*i+m*j+o*h,r=c*i+n*j+p*h,s=m*k+o*l,t=n*k+p*l,u=o*l,v=p*l,w=19*a,x=this.curves;x[w++]=2;for(var y=q,z=r,A=w+19-1;A>w;w+=2)x[w]=y,x[w+1]=z,q+=s,r+=t,s+=u,t+=v,y+=q,z+=r},getCurvePercent:function(a,b){b=0>b?0:b>1?1:b;var c=this.curves,d=19*a,e=c[d];if(0===e)return b;if(1==e)return 0;d++;for(var f=0,g=d,h=d+19-1;h>d;d+=2)if(f=c[d],f>=b){var i,j;return d==g?(i=0,j=0):(i=c[d-2],j=c[d-1]),j+(c[d+1]-j)*(b-i)/(f-i)}var k=c[d-1];return k+(1-k)*(b-f)/(1-f)}},c.RotateTimeline=function(a){this.curves=new c.Curves(a),this.frames=[],this.frames.length=2*a},c.RotateTimeline.prototype={boneIndex:0,getFrameCount:function(){return this.frames.length/2},setFrame:function(a,b,c){a*=2,this.frames[a]=b,this.frames[a+1]=c},apply:function(a,b,d,e,f){var g=this.frames;if(!(d=g[g.length-2]){for(var i=h.data.rotation+g[g.length-1]-h.rotation;i>180;)i-=360;for(;-180>i;)i+=360;return void(h.rotation+=i*f)}var j=c.Animation.binarySearch(g,d,2),k=g[j-1],l=g[j],m=1-(d-l)/(g[j-2]-l);m=this.curves.getCurvePercent(j/2-1,m);for(var i=g[j+1]-k;i>180;)i-=360;for(;-180>i;)i+=360;for(i=h.data.rotation+(k+i*m)-h.rotation;i>180;)i-=360;for(;-180>i;)i+=360;h.rotation+=i*f}}},c.TranslateTimeline=function(a){this.curves=new c.Curves(a),this.frames=[],this.frames.length=3*a},c.TranslateTimeline.prototype={boneIndex:0,getFrameCount:function(){return this.frames.length/3},setFrame:function(a,b,c,d){a*=3,this.frames[a]=b,this.frames[a+1]=c,this.frames[a+2]=d},apply:function(a,b,d,e,f){var g=this.frames;if(!(d=g[g.length-3])return h.x+=(h.data.x+g[g.length-2]-h.x)*f,void(h.y+=(h.data.y+g[g.length-1]-h.y)*f);var i=c.Animation.binarySearch(g,d,3),j=g[i-2],k=g[i-1],l=g[i],m=1-(d-l)/(g[i+-3]-l);m=this.curves.getCurvePercent(i/3-1,m),h.x+=(h.data.x+j+(g[i+1]-j)*m-h.x)*f,h.y+=(h.data.y+k+(g[i+2]-k)*m-h.y)*f}}},c.ScaleTimeline=function(a){this.curves=new c.Curves(a),this.frames=[],this.frames.length=3*a},c.ScaleTimeline.prototype={boneIndex:0,getFrameCount:function(){return this.frames.length/3},setFrame:function(a,b,c,d){a*=3,this.frames[a]=b,this.frames[a+1]=c,this.frames[a+2]=d},apply:function(a,b,d,e,f){var g=this.frames;if(!(d=g[g.length-3])return h.scaleX+=(h.data.scaleX*g[g.length-2]-h.scaleX)*f,void(h.scaleY+=(h.data.scaleY*g[g.length-1]-h.scaleY)*f);var i=c.Animation.binarySearch(g,d,3),j=g[i-2],k=g[i-1],l=g[i],m=1-(d-l)/(g[i+-3]-l);m=this.curves.getCurvePercent(i/3-1,m),h.scaleX+=(h.data.scaleX*(j+(g[i+1]-j)*m)-h.scaleX)*f,h.scaleY+=(h.data.scaleY*(k+(g[i+2]-k)*m)-h.scaleY)*f}}},c.ColorTimeline=function(a){this.curves=new c.Curves(a),this.frames=[],this.frames.length=5*a},c.ColorTimeline.prototype={slotIndex:0,getFrameCount:function(){return this.frames.length/5},setFrame:function(a,b,c,d,e,f){a*=5,this.frames[a]=b,this.frames[a+1]=c,this.frames[a+2]=d,this.frames[a+3]=e,this.frames[a+4]=f},apply:function(a,b,d,e,f){var g=this.frames;if(!(d=g[g.length-5]){var l=g.length-1;h=g[l-3],i=g[l-2],j=g[l-1],k=g[l]}else{var m=c.Animation.binarySearch(g,d,5),n=g[m-4],o=g[m-3],p=g[m-2],q=g[m-1],r=g[m],s=1-(d-r)/(g[m-5]-r);s=this.curves.getCurvePercent(m/5-1,s),h=n+(g[m+1]-n)*s,i=o+(g[m+2]-o)*s,j=p+(g[m+3]-p)*s,k=q+(g[m+4]-q)*s}var t=a.slots[this.slotIndex];1>f?(t.r+=(h-t.r)*f,t.g+=(i-t.g)*f,t.b+=(j-t.b)*f,t.a+=(k-t.a)*f):(t.r=h,t.g=i,t.b=j,t.a=k)}}},c.AttachmentTimeline=function(a){this.curves=new c.Curves(a),this.frames=[],this.frames.length=a,this.attachmentNames=[],this.attachmentNames.length=a},c.AttachmentTimeline.prototype={slotIndex:0,getFrameCount:function(){return this.frames.length},setFrame:function(a,b,c){this.frames[a]=b,this.attachmentNames[a]=c},apply:function(a,b,d){var e=this.frames;if(dd&&this.apply(a,b,Number.MAX_VALUE,null,0));b>d&&(b=-1);var f=d>=e[e.length-1]?e.length-1:c.Animation.binarySearch1(e,d)-1;if(!(e[f]d)this.apply(a,b,Number.MAX_VALUE,e,f),b=-1;else if(b>=g[h-1])return;if(!(di&&d>=g[i];i++)e.push(k[i])}}}},c.DrawOrderTimeline=function(a){this.frames=[],this.frames.length=a,this.drawOrders=[],this.drawOrders.length=a},c.DrawOrderTimeline.prototype={getFrameCount:function(){return this.frames.length},setFrame:function(a,b,c){this.frames[a]=b,this.drawOrders[a]=c},apply:function(a,b,d){var e=this.frames;if(!(d=e[e.length-1]?e.length-1:c.Animation.binarySearch1(e,d)-1;var g=a.drawOrder,h=a.slots,i=this.drawOrders[f];if(i)for(var j=0,k=i.length;k>j;j++)g[j]=a.slots[i[j]];else for(var j=0,k=h.length;k>j;j++)g[j]=h[j]}}},c.FfdTimeline=function(a){this.curves=new c.Curves(a),this.frames=[],this.frames.length=a,this.frameVertices=[],this.frameVertices.length=a},c.FfdTimeline.prototype={slotIndex:0,attachment:0,getFrameCount:function(){return this.frames.length},setFrame:function(a,b,c){this.frames[a]=b,this.frameVertices[a]=c},apply:function(a,b,d,e,f){var g=a.slots[this.slotIndex];if(g.attachment==this.attachment){var h=this.frames;if(!(d=h[h.length-1]){var l=i[h.length-1];if(1>f)for(var m=0;j>m;m++)k[m]+=(l[m]-k[m])*f;else for(var m=0;j>m;m++)k[m]=l[m]}else{var n=c.Animation.binarySearch1(h,d),o=h[n],p=1-(d-o)/(h[n-1]-o);p=this.curves.getCurvePercent(n-1,0>p?0:p>1?1:p);var q=i[n-1],r=i[n];if(1>f)for(var m=0;j>m;m++){var s=q[m];k[m]+=(s+(r[m]-s)*p-k[m])*f}else for(var m=0;j>m;m++){var s=q[m];k[m]=s+(r[m]-s)*p}}}}}},c.IkConstraintTimeline=function(a){this.curves=new c.Curves(a),this.frames=[],this.frames.length=3*a},c.IkConstraintTimeline.prototype={ikConstraintIndex:0,getFrameCount:function(){return this.frames.length/3},setFrame:function(a,b,c,d){a*=3,this.frames[a]=b,this.frames[a+1]=c,this.frames[a+2]=d},apply:function(a,b,d,e,f){var g=this.frames;if(!(d=g[g.length-3])return h.mix+=(g[g.length-2]-h.mix)*f,void(h.bendDirection=g[g.length-1]);var i=c.Animation.binarySearch(g,d,3),j=g[i+-2],k=g[i],l=1-(d-k)/(g[i+-3]-k);l=this.curves.getCurvePercent(i/3-1,l);var m=j+(g[i+1]-j)*l;h.mix+=(m-h.mix)*f,h.bendDirection=g[i+-1]}}},c.FlipXTimeline=function(a){this.curves=new c.Curves(a),this.frames=[],this.frames.length=2*a},c.FlipXTimeline.prototype={boneIndex:0,getFrameCount:function(){return this.frames.length/2},setFrame:function(a,b,c){a*=2,this.frames[a]=b,this.frames[a+1]=c?1:0},apply:function(a,b,d){var e=this.frames;if(dd&&this.apply(a,b,Number.MAX_VALUE,null,0));b>d&&(b=-1);var f=(d>=e[e.length-2]?e.length:c.Animation.binarySearch(e,d,2))-2;e[f]d&&(b=-1);var f=(d>=e[e.length-2]?e.length:c.Animation.binarySearch(e,d,2))-2;e[f]c;c++)if(b[c].name==a)return b[c];return null},findBoneIndex:function(a){for(var b=this.bones,c=0,d=b.length;d>c;c++)if(b[c].name==a)return c;return-1},findSlot:function(a){for(var b=this.slots,c=0,d=b.length;d>c;c++)if(b[c].name==a)return slot[c];return null},findSlotIndex:function(a){for(var b=this.slots,c=0,d=b.length;d>c;c++)if(b[c].name==a)return c;return-1},findSkin:function(a){for(var b=this.skins,c=0,d=b.length;d>c;c++)if(b[c].name==a)return b[c];return null},findEvent:function(a){for(var b=this.events,c=0,d=b.length;d>c;c++)if(b[c].name==a)return b[c];return null},findAnimation:function(a){for(var b=this.animations,c=0,d=b.length;d>c;c++)if(b[c].name==a)return b[c];return null},findIkConstraint:function(a){for(var b=this.ikConstraints,c=0,d=b.length;d>c;c++)if(b[c].name==a)return b[c];return null}},c.Skeleton=function(a){this.data=a,this.bones=[];for(var b=0,d=a.bones.length;d>b;b++){var e=a.bones[b],f=e.parent?this.bones[a.bones.indexOf(e.parent)]:null;this.bones.push(new c.Bone(e,this,f))}this.slots=[],this.drawOrder=[];for(var b=0,d=a.slots.length;d>b;b++){var g=a.slots[b],h=this.bones[a.bones.indexOf(g.boneData)],i=new c.Slot(g,h);this.slots.push(i),this.drawOrder.push(i)}this.ikConstraints=[];for(var b=0,d=a.ikConstraints.length;d>b;b++)this.ikConstraints.push(new c.IkConstraint(a.ikConstraints[b],this));this.boneCache=[],this.updateCache()},c.Skeleton.prototype={x:0,y:0,skin:null,r:1,g:1,b:1,a:1,time:0,flipX:!1,flipY:!1,updateCache:function(){var a=this.ikConstraints,b=a.length,c=b+1,d=this.boneCache;d.length>c&&(d.length=c);for(var e=0,f=d.length;f>e;e++)d[e].length=0;for(;d.lengthe;e++){var i=h[e],j=i;do{for(var k=0;b>k;k++)for(var l=a[k],m=l.bones[0],n=l.bones[l.bones.length-1];;){if(j==n){d[k].push(i),d[k+1].push(i);continue a}if(n==m)break;n=n.parent}j=j.parent}while(j);g[g.length]=i}},updateWorldTransform:function(){for(var a=this.bones,b=0,c=a.length;c>b;b++){var d=a[b];d.rotationIK=d.rotation}for(var b=0,e=this.boneCache.length-1;;){for(var f=this.boneCache[b],g=0,h=f.length;h>g;g++)f[g].updateWorldTransform();if(b==e)break;this.ikConstraints[b].apply(),b++}},setToSetupPose:function(){this.setBonesToSetupPose(),this.setSlotsToSetupPose()},setBonesToSetupPose:function(){for(var a=this.bones,b=0,c=a.length;c>b;b++)a[b].setToSetupPose();for(var d=this.ikConstraints,b=0,c=d.length;c>b;b++){var e=d[b];e.bendDirection=e.data.bendDirection,e.mix=e.data.mix}},setSlotsToSetupPose:function(){for(var a=this.slots,b=this.drawOrder,c=0,d=a.length;d>c;c++)b[c]=a[c],a[c].setToSetupPose(c)},getRootBone:function(){return this.bones.length?this.bones[0]:null},findBone:function(a){for(var b=this.bones,c=0,d=b.length;d>c;c++)if(b[c].data.name==a)return b[c];return null},findBoneIndex:function(a){for(var b=this.bones,c=0,d=b.length;d>c;c++)if(b[c].data.name==a)return c;return-1},findSlot:function(a){for(var b=this.slots,c=0,d=b.length;d>c;c++)if(b[c].data.name==a)return b[c];return null},findSlotIndex:function(a){for(var b=this.slots,c=0,d=b.length;d>c;c++)if(b[c].data.name==a)return c;return-1},setSkinByName:function(a){var b=this.data.findSkin(a);if(!b)throw\"Skin not found: \"+a;this.setSkin(b)},setSkin:function(a){if(a)if(this.skin)a._attachAll(this,this.skin);else for(var b=this.slots,c=0,d=b.length;d>c;c++){var e=b[c],f=e.data.attachmentName;if(f){var g=a.getAttachment(c,f);g&&e.setAttachment(g)}}this.skin=a},getAttachmentBySlotName:function(a,b){return this.getAttachmentBySlotIndex(this.data.findSlotIndex(a),b)},getAttachmentBySlotIndex:function(a,b){if(this.skin){var c=this.skin.getAttachment(a,b);if(c)return c}return this.data.defaultSkin?this.data.defaultSkin.getAttachment(a,b):null},setAttachment:function(a,b){for(var c=this.slots,d=0,e=c.length;e>d;d++){var f=c[d];if(f.data.name==a){var g=null;if(b&&(g=this.getAttachmentBySlotIndex(d,b),!g))throw\"Attachment not found: \"+b+\", for slot: \"+a;return void f.setAttachment(g)}}throw\"Slot not found: \"+a},findIkConstraint:function(a){for(var b=this.ikConstraints,c=0,d=b.length;d>c;c++)if(b[c].data.name==a)return b[c];return null},update:function(a){this.time+=a}},c.EventData=function(a){this.name=a},c.EventData.prototype={intValue:0,floatValue:0,stringValue:null},c.Event=function(a){this.data=a},c.Event.prototype={intValue:0,floatValue:0,stringValue:null},c.AttachmentType={region:0,boundingbox:1,mesh:2,skinnedmesh:3},c.RegionAttachment=function(a){this.name=a,this.offset=[],this.offset.length=8,this.uvs=[],this.uvs.length=8},c.RegionAttachment.prototype={type:c.AttachmentType.region,x:0,y:0,rotation:0,scaleX:1,scaleY:1,width:0,height:0,r:1,g:1,b:1,a:1,path:null,rendererObject:null,regionOffsetX:0,regionOffsetY:0,regionWidth:0,regionHeight:0,regionOriginalWidth:0,regionOriginalHeight:0,setUVs:function(a,b,c,d,e){var f=this.uvs;e?(f[2]=a,f[3]=d,f[4]=a,f[5]=b,f[6]=c,f[7]=b,f[0]=c,f[1]=d):(f[0]=a,f[1]=d,f[2]=a,f[3]=b,f[4]=c,f[5]=b,f[6]=c,f[7]=d)},updateOffset:function(){var a=this.width/this.regionOriginalWidth*this.scaleX,b=this.height/this.regionOriginalHeight*this.scaleY,d=-this.width/2*this.scaleX+this.regionOffsetX*a,e=-this.height/2*this.scaleY+this.regionOffsetY*b,f=d+this.regionWidth*a,g=e+this.regionHeight*b,h=this.rotation*c.degRad,i=Math.cos(h),j=Math.sin(h),k=d*i+this.x,l=d*j,m=e*i+this.y,n=e*j,o=f*i+this.x,p=f*j,q=g*i+this.y,r=g*j,s=this.offset;s[0]=k-n,s[1]=m+l,s[2]=k-r,s[3]=q+l,s[4]=o-r,s[5]=q+p,s[6]=o-n,s[7]=m+p},computeVertices:function(a,b,c,d){a+=c.worldX,b+=c.worldY;var e=c.m00,f=c.m01,g=c.m10,h=c.m11,i=this.offset;d[0]=i[0]*e+i[1]*f+a,d[1]=i[0]*g+i[1]*h+b,d[2]=i[2]*e+i[3]*f+a,d[3]=i[2]*g+i[3]*h+b,d[4]=i[4]*e+i[5]*f+a,d[5]=i[4]*g+i[5]*h+b,d[6]=i[6]*e+i[7]*f+a,d[7]=i[6]*g+i[7]*h+b}},c.MeshAttachment=function(a){this.name=a},c.MeshAttachment.prototype={type:c.AttachmentType.mesh,vertices:null,uvs:null,regionUVs:null,triangles:null,hullLength:0,r:1,g:1,b:1,a:1,path:null,rendererObject:null,regionU:0,regionV:0,regionU2:0,regionV2:0,regionRotate:!1,regionOffsetX:0,regionOffsetY:0,regionWidth:0,regionHeight:0,regionOriginalWidth:0,regionOriginalHeight:0,edges:null,width:0,height:0,updateUVs:function(){var a=this.regionU2-this.regionU,b=this.regionV2-this.regionV,d=this.regionUVs.length;if(this.uvs&&this.uvs.length==d||(this.uvs=new c.Float32Array(d)),this.regionRotate)for(var e=0;d>e;e+=2)this.uvs[e]=this.regionU+this.regionUVs[e+1]*a,this.uvs[e+1]=this.regionV+b-this.regionUVs[e]*b;else for(var e=0;d>e;e+=2)this.uvs[e]=this.regionU+this.regionUVs[e]*a,this.uvs[e+1]=this.regionV+this.regionUVs[e+1]*b},computeWorldVertices:function(a,b,c,d){var e=c.bone;a+=e.worldX,b+=e.worldY;var f=e.m00,g=e.m01,h=e.m10,i=e.m11,j=this.vertices,k=j.length;c.attachmentVertices.length==k&&(j=c.attachmentVertices);for(var l=0;k>l;l+=2){var m=j[l],n=j[l+1];d[l]=m*f+n*g+a,d[l+1]=m*h+n*i+b}}},c.SkinnedMeshAttachment=function(a){this.name=a},c.SkinnedMeshAttachment.prototype={type:c.AttachmentType.skinnedmesh,bones:null,weights:null,uvs:null,regionUVs:null,triangles:null,hullLength:0,r:1,g:1,b:1,a:1,path:null,rendererObject:null,regionU:0,regionV:0,regionU2:0,regionV2:0,regionRotate:!1,regionOffsetX:0,regionOffsetY:0,regionWidth:0,regionHeight:0,regionOriginalWidth:0,regionOriginalHeight:0,edges:null,width:0,height:0,updateUVs:function(){var a=this.regionU2-this.regionU,b=this.regionV2-this.regionV,d=this.regionUVs.length;if(this.uvs&&this.uvs.length==d||(this.uvs=new c.Float32Array(d)),this.regionRotate)for(var e=0;d>e;e+=2)this.uvs[e]=this.regionU+this.regionUVs[e+1]*a,this.uvs[e+1]=this.regionV+b-this.regionUVs[e]*b;else for(var e=0;d>e;e+=2)this.uvs[e]=this.regionU+this.regionUVs[e]*a,this.uvs[e+1]=this.regionV+this.regionUVs[e+1]*b},computeWorldVertices:function(a,b,c,d){var e,f,g,h,i,j,k,l=c.bone.skeleton.bones,m=this.weights,n=this.bones,o=0,p=0,q=0,r=0,s=n.length;if(c.attachmentVertices.length)for(var t=c.attachmentVertices;s>p;o+=2){for(f=0,g=0,e=n[p++]+p;e>p;p++,q+=3,r+=2)h=l[n[p]],i=m[q]+t[r],j=m[q+1]+t[r+1],k=m[q+2],f+=(i*h.m00+j*h.m01+h.worldX)*k,g+=(i*h.m10+j*h.m11+h.worldY)*k;d[o]=f+a,d[o+1]=g+b}else for(;s>p;o+=2){for(f=0,g=0,e=n[p++]+p;e>p;p++,q+=3)h=l[n[p]],i=m[q],j=m[q+1],k=m[q+2],f+=(i*h.m00+j*h.m01+h.worldX)*k,g+=(i*h.m10+j*h.m11+h.worldY)*k;d[o]=f+a,d[o+1]=g+b}}},c.BoundingBoxAttachment=function(a){this.name=a,this.vertices=[]},c.BoundingBoxAttachment.prototype={type:c.AttachmentType.boundingbox,computeWorldVertices:function(a,b,c,d){a+=c.worldX,b+=c.worldY;for(var e=c.m00,f=c.m01,g=c.m10,h=c.m11,i=this.vertices,j=0,k=i.length;k>j;j+=2){var l=i[j],m=i[j+1];d[j]=l*e+m*f+a,d[j+1]=l*g+m*h+b}}},c.AnimationStateData=function(a){this.skeletonData=a,this.animationToMixTime={}},c.AnimationStateData.prototype={defaultMix:0,setMixByName:function(a,b,c){var d=this.skeletonData.findAnimation(a);if(!d)throw\"Animation not found: \"+a;var e=this.skeletonData.findAnimation(b);if(!e)throw\"Animation not found: \"+b;this.setMix(d,e,c)},setMix:function(a,b,c){this.animationToMixTime[a.name+\":\"+b.name]=c},getMix:function(a,b){var c=a.name+\":\"+b.name;return this.animationToMixTime.hasOwnProperty(c)?this.animationToMixTime[c]:this.defaultMix}},c.TrackEntry=function(){},c.TrackEntry.prototype={next:null,previous:null,animation:null,loop:!1,delay:0,time:0,lastTime:-1,endTime:0,timeScale:1,mixTime:0,mixDuration:0,mix:1,onStart:null,onEnd:null,onComplete:null,onEvent:null},c.AnimationState=function(a){this.data=a,this.tracks=[],this.events=[]},c.AnimationState.prototype={onStart:null,onEnd:null,onComplete:null,onEvent:null,timeScale:1,update:function(a){a*=this.timeScale;for(var b=0;b=0&&this.setCurrent(b,e)):!c.loop&&c.lastTime>=c.endTime&&this.clearTrack(b)}}},apply:function(a){for(var b=0;bf&&(d=f);var h=c.previous;if(h){var i=h.time;!h.loop&&i>h.endTime&&(i=h.endTime),h.animation.apply(a,i,i,h.loop,null);var j=c.mixTime/c.mixDuration*c.mix;j>=1&&(j=1,c.previous=null),c.animation.mix(a,c.lastTime,d,g,this.events,j)}else 1==c.mix?c.animation.apply(a,c.lastTime,d,g,this.events):c.animation.mix(a,c.lastTime,d,g,this.events,c.mix);for(var k=0,l=this.events.length;l>k;k++){var m=this.events[k];c.onEvent&&c.onEvent(b,m),this.onEvent&&this.onEvent(b,m)}if(g?e%f>d%f:f>e&&d>=f){var n=Math.floor(d/f);c.onComplete&&c.onComplete(b,n),this.onComplete&&this.onComplete(b,n)}c.lastTime=c.time}}},clearTracks:function(){for(var a=0,b=this.tracks.length;b>a;a++)this.clearTrack(a);this.tracks.length=0},clearTrack:function(a){if(!(a>=this.tracks.length)){var b=this.tracks[a];b&&(b.onEnd&&b.onEnd(a),this.onEnd&&this.onEnd(a),this.tracks[a]=null)}},_expandToIndex:function(a){if(a=this.tracks.length;)this.tracks.push(null);return null},setCurrent:function(a,b){var c=this._expandToIndex(a);if(c){var d=c.previous;c.previous=null,c.onEnd&&c.onEnd(a),this.onEnd&&this.onEnd(a),b.mixDuration=this.data.getMix(c.animation,b.animation),b.mixDuration>0&&(b.mixTime=0,b.previous=d&&c.mixTime/c.mixDuration=e&&(g?e+=g.endTime-this.data.getMix(g.animation,b):e=0),f.delay=e,f},getCurrent:function(a){return a>=this.tracks.length?null:this.tracks[a]}},c.SkeletonJson=function(a){this.attachmentLoader=a},c.SkeletonJson.prototype={scale:1,readSkeletonData:function(a,b){var d=new c.SkeletonData;d.name=b;var e=a.skeleton;e&&(d.hash=e.hash,d.version=e.spine,d.width=e.width||0,d.height=e.height||0);for(var f=a.bones,g=0,h=f.length;h>g;g++){var i=f[g],j=null;if(i.parent&&(j=d.findBone(i.parent),!j))throw\"Parent bone not found: \"+i.parent;var k=new c.BoneData(i.name,j);k.length=(i.length||0)*this.scale,k.x=(i.x||0)*this.scale,k.y=(i.y||0)*this.scale,k.rotation=i.rotation||0,k.scaleX=i.hasOwnProperty(\"scaleX\")?i.scaleX:1,k.scaleY=i.hasOwnProperty(\"scaleY\")?i.scaleY:1,k.inheritScale=i.hasOwnProperty(\"inheritScale\")?i.inheritScale:!0,k.inheritRotation=i.hasOwnProperty(\"inheritRotation\")?i.inheritRotation:!0,d.bones.push(k)}var l=a.ik;if(l)for(var g=0,h=l.length;h>g;g++){for(var m=l[g],n=new c.IkConstraintData(m.name),f=m.bones,o=0,p=f.length;p>o;o++){var q=d.findBone(f[o]);if(!q)throw\"IK bone not found: \"+f[o];n.bones.push(q)}if(n.target=d.findBone(m.target),!n.target)throw\"Target bone not found: \"+m.target;n.bendDirection=!m.hasOwnProperty(\"bendPositive\")||m.bendPositive?1:-1,n.mix=m.hasOwnProperty(\"mix\")?m.mix:1,d.ikConstraints.push(n)}for(var r=a.slots,g=0,h=r.length;h>g;g++){var s=r[g],k=d.findBone(s.bone);if(!k)throw\"Slot bone not found: \"+s.bone;var t=new c.SlotData(s.name,k),u=s.color;u&&(t.r=this.toColor(u,0),t.g=this.toColor(u,1),t.b=this.toColor(u,2),t.a=this.toColor(u,3)),t.attachmentName=s.attachment,t.additiveBlending=s.additive&&\"true\"==s.additive,d.slots.push(t)}var v=a.skins;for(var w in v)if(v.hasOwnProperty(w)){var x=v[w],y=new c.Skin(w);for(var z in x)if(x.hasOwnProperty(z)){var A=d.findSlotIndex(z),B=x[z];for(var C in B)if(B.hasOwnProperty(C)){var D=this.readAttachment(y,C,B[C]);D&&y.addAttachment(A,C,D)}}d.skins.push(y),\"default\"==y.name&&(d.defaultSkin=y)}var E=a.events;for(var F in E)if(E.hasOwnProperty(F)){var G=E[F],H=new c.EventData(F);H.intValue=G[\"int\"]||0,H.floatValue=G[\"float\"]||0,H.stringValue=G.string||null,d.events.push(H)}var I=a.animations;for(var J in I)I.hasOwnProperty(J)&&this.readAnimation(J,I[J],d);return d},readAttachment:function(a,b,d){b=d.name||b;var e=c.AttachmentType[d.type||\"region\"],f=d.path||b,g=this.scale;if(e==c.AttachmentType.region){var h=this.attachmentLoader.newRegionAttachment(a,b,f);if(!h)return null;h.path=f,h.x=(d.x||0)*g,h.y=(d.y||0)*g,h.scaleX=d.hasOwnProperty(\"scaleX\")?d.scaleX:1,h.scaleY=d.hasOwnProperty(\"scaleY\")?d.scaleY:1,h.rotation=d.rotation||0,h.width=(d.width||0)*g,h.height=(d.height||0)*g;var i=d.color;return i&&(h.r=this.toColor(i,0),h.g=this.toColor(i,1),h.b=this.toColor(i,2),h.a=this.toColor(i,3)),h.updateOffset(),h}if(e==c.AttachmentType.mesh){var j=this.attachmentLoader.newMeshAttachment(a,b,f);return j?(j.path=f,j.vertices=this.getFloatArray(d,\"vertices\",g),j.triangles=this.getIntArray(d,\"triangles\"),j.regionUVs=this.getFloatArray(d,\"uvs\",1),j.updateUVs(),i=d.color,i&&(j.r=this.toColor(i,0),j.g=this.toColor(i,1),j.b=this.toColor(i,2),j.a=this.toColor(i,3)),j.hullLength=2*(d.hull||0),d.edges&&(j.edges=this.getIntArray(d,\"edges\")),j.width=(d.width||0)*g,j.height=(d.height||0)*g,j):null}if(e==c.AttachmentType.skinnedmesh){var j=this.attachmentLoader.newSkinnedMeshAttachment(a,b,f);if(!j)return null;j.path=f;for(var k=this.getFloatArray(d,\"uvs\",1),l=this.getFloatArray(d,\"vertices\",1),m=[],n=[],o=0,p=l.length;p>o;){var q=0|l[o++];n[n.length]=q;for(var r=o+4*q;r>o;)n[n.length]=l[o],m[m.length]=l[o+1]*g,m[m.length]=l[o+2]*g,m[m.length]=l[o+3],o+=4 }return j.bones=n,j.weights=m,j.triangles=this.getIntArray(d,\"triangles\"),j.regionUVs=k,j.updateUVs(),i=d.color,i&&(j.r=this.toColor(i,0),j.g=this.toColor(i,1),j.b=this.toColor(i,2),j.a=this.toColor(i,3)),j.hullLength=2*(d.hull||0),d.edges&&(j.edges=this.getIntArray(d,\"edges\")),j.width=(d.width||0)*g,j.height=(d.height||0)*g,j}if(e==c.AttachmentType.boundingbox){for(var s=this.attachmentLoader.newBoundingBoxAttachment(a,b),l=d.vertices,o=0,p=l.length;p>o;o++)s.vertices.push(l[o]*g);return s}throw\"Unknown attachment type: \"+e},readAnimation:function(a,b,d){var e=[],f=0,g=b.slots;for(var h in g)if(g.hasOwnProperty(h)){var i=g[h],j=d.findSlotIndex(h);for(var k in i)if(i.hasOwnProperty(k)){var l=i[k];if(\"color\"==k){var m=new c.ColorTimeline(l.length);m.slotIndex=j;for(var n=0,o=0,p=l.length;p>o;o++){var q=l[o],r=q.color,s=this.toColor(r,0),t=this.toColor(r,1),u=this.toColor(r,2),v=this.toColor(r,3);m.setFrame(n,q.time,s,t,u,v),this.readCurve(m,n,q),n++}e.push(m),f=Math.max(f,m.frames[5*m.getFrameCount()-5])}else{if(\"attachment\"!=k)throw\"Invalid timeline type for a slot: \"+k+\" (\"+h+\")\";var m=new c.AttachmentTimeline(l.length);m.slotIndex=j;for(var n=0,o=0,p=l.length;p>o;o++){var q=l[o];m.setFrame(n++,q.time,q.name)}e.push(m),f=Math.max(f,m.frames[m.getFrameCount()-1])}}}var w=b.bones;for(var x in w)if(w.hasOwnProperty(x)){var y=d.findBoneIndex(x);if(-1==y)throw\"Bone not found: \"+x;var z=w[x];for(var k in z)if(z.hasOwnProperty(k)){var l=z[k];if(\"rotate\"==k){var m=new c.RotateTimeline(l.length);m.boneIndex=y;for(var n=0,o=0,p=l.length;p>o;o++){var q=l[o];m.setFrame(n,q.time,q.angle),this.readCurve(m,n,q),n++}e.push(m),f=Math.max(f,m.frames[2*m.getFrameCount()-2])}else if(\"translate\"==k||\"scale\"==k){var m,A=1;\"scale\"==k?m=new c.ScaleTimeline(l.length):(m=new c.TranslateTimeline(l.length),A=this.scale),m.boneIndex=y;for(var n=0,o=0,p=l.length;p>o;o++){var q=l[o],B=(q.x||0)*A,C=(q.y||0)*A;m.setFrame(n,q.time,B,C),this.readCurve(m,n,q),n++}e.push(m),f=Math.max(f,m.frames[3*m.getFrameCount()-3])}else{if(\"flipX\"!=k&&\"flipY\"!=k)throw\"Invalid timeline type for a bone: \"+k+\" (\"+x+\")\";var B=\"flipX\"==k,m=B?new c.FlipXTimeline(l.length):new c.FlipYTimeline(l.length);m.boneIndex=y;for(var D=B?\"x\":\"y\",n=0,o=0,p=l.length;p>o;o++){var q=l[o];m.setFrame(n,q.time,q[D]||!1),n++}e.push(m),f=Math.max(f,m.frames[2*m.getFrameCount()-2])}}}var E=b.ik;for(var F in E)if(E.hasOwnProperty(F)){var G=d.findIkConstraint(F),l=E[F],m=new c.IkConstraintTimeline(l.length);m.ikConstraintIndex=d.ikConstraints.indexOf(G);for(var n=0,o=0,p=l.length;p>o;o++){var q=l[o],H=q.hasOwnProperty(\"mix\")?q.mix:1,I=!q.hasOwnProperty(\"bendPositive\")||q.bendPositive?1:-1;m.setFrame(n,q.time,H,I),this.readCurve(m,n,q),n++}e.push(m),f=Math.max(f,m.frames[3*m.frameCount-3])}var J=b.ffd;for(var K in J){var L=d.findSkin(K),i=J[K];for(h in i){var j=d.findSlotIndex(h),M=i[h];for(var N in M){var l=M[N],m=new c.FfdTimeline(l.length),O=L.getAttachment(j,N);if(!O)throw\"FFD attachment not found: \"+N;m.slotIndex=j,m.attachment=O;var P,Q=O.type==c.AttachmentType.mesh;P=Q?O.vertices.length:O.weights.length/3*2;for(var n=0,o=0,p=l.length;p>o;o++){var R,q=l[o];if(q.vertices){var S=q.vertices,R=[];R.length=P;var T=q.offset||0,U=S.length;if(1==this.scale)for(var V=0;U>V;V++)R[V+T]=S[V];else for(var V=0;U>V;V++)R[V+T]=S[V]*this.scale;if(Q)for(var W=O.vertices,V=0,U=R.length;U>V;V++)R[V]+=W[V]}else Q?R=O.vertices:(R=[],R.length=P);m.setFrame(n,q.time,R),this.readCurve(m,n,q),n++}e[e.length]=m,f=Math.max(f,m.frames[m.frameCount-1])}}}var X=b.drawOrder;if(X||(X=b.draworder),X){for(var m=new c.DrawOrderTimeline(X.length),Y=d.slots.length,n=0,o=0,p=X.length;p>o;o++){var Z=X[o],$=null;if(Z.offsets){$=[],$.length=Y;for(var V=Y-1;V>=0;V--)$[V]=-1;var _=Z.offsets,ab=[];ab.length=Y-_.length;for(var bb=0,cb=0,V=0,U=_.length;U>V;V++){var db=_[V],j=d.findSlotIndex(db.slot);if(-1==j)throw\"Slot not found: \"+db.slot;for(;bb!=j;)ab[cb++]=bb++;$[bb+db.offset]=bb++}for(;Y>bb;)ab[cb++]=bb++;for(var V=Y-1;V>=0;V--)-1==$[V]&&($[V]=ab[--cb])}m.setFrame(n++,Z.time,$)}e.push(m),f=Math.max(f,m.frames[m.getFrameCount()-1])}var eb=b.events;if(eb){for(var m=new c.EventTimeline(eb.length),n=0,o=0,p=eb.length;p>o;o++){var fb=eb[o],gb=d.findEvent(fb.name);if(!gb)throw\"Event not found: \"+fb.name;var hb=new c.Event(gb);hb.intValue=fb.hasOwnProperty(\"int\")?fb[\"int\"]:gb.intValue,hb.floatValue=fb.hasOwnProperty(\"float\")?fb[\"float\"]:gb.floatValue,hb.stringValue=fb.hasOwnProperty(\"string\")?fb.string:gb.stringValue,m.setFrame(n++,fb.time,hb)}e.push(m),f=Math.max(f,m.frames[m.getFrameCount()-1])}d.animations.push(new c.Animation(a,e,f))},readCurve:function(a,b,c){var d=c.curve;d?\"stepped\"==d?a.curves.setStepped(b):d instanceof Array&&a.curves.setCurve(b,d[0],d[1],d[2],d[3]):a.curves.setLinear(b)},toColor:function(a,b){if(8!=a.length)throw\"Color hexidecimal length must be 8, recieved: \"+a;return parseInt(a.substring(2*b,2*b+2),16)/255},getFloatArray:function(a,b,d){var e=a[b],f=new c.Float32Array(e.length),g=0,h=e.length;if(1==d)for(;h>g;g++)f[g]=e[g];else for(;h>g;g++)f[g]=e[g]*d;return f},getIntArray:function(a,b){for(var d=a[b],e=new c.Uint16Array(d.length),f=0,g=d.length;g>f;f++)e[f]=0|d[f];return e}},c.Atlas=function(a,b){this.textureLoader=b,this.pages=[],this.regions=[];var d=new c.AtlasReader(a),e=[];e.length=4;for(var f=null;;){var g=d.readLine();if(null===g)break;if(g=d.trim(g),g.length)if(f){var h=new c.AtlasRegion;h.name=g,h.page=f,h.rotate=\"true\"==d.readValue(),d.readTuple(e);var i=parseInt(e[0]),j=parseInt(e[1]);d.readTuple(e);var k=parseInt(e[0]),l=parseInt(e[1]);h.u=i/f.width,h.v=j/f.height,h.rotate?(h.u2=(i+l)/f.width,h.v2=(j+k)/f.height):(h.u2=(i+k)/f.width,h.v2=(j+l)/f.height),h.x=i,h.y=j,h.width=Math.abs(k),h.height=Math.abs(l),4==d.readTuple(e)&&(h.splits=[parseInt(e[0]),parseInt(e[1]),parseInt(e[2]),parseInt(e[3])],4==d.readTuple(e)&&(h.pads=[parseInt(e[0]),parseInt(e[1]),parseInt(e[2]),parseInt(e[3])],d.readTuple(e))),h.originalWidth=parseInt(e[0]),h.originalHeight=parseInt(e[1]),d.readTuple(e),h.offsetX=parseInt(e[0]),h.offsetY=parseInt(e[1]),h.index=parseInt(d.readValue()),this.regions.push(h)}else{f=new c.AtlasPage,f.name=g,2==d.readTuple(e)&&(f.width=parseInt(e[0]),f.height=parseInt(e[1]),d.readTuple(e)),f.format=c.Atlas.Format[e[0]],d.readTuple(e),f.minFilter=c.Atlas.TextureFilter[e[0]],f.magFilter=c.Atlas.TextureFilter[e[1]];var m=d.readValue();f.uWrap=c.Atlas.TextureWrap.clampToEdge,f.vWrap=c.Atlas.TextureWrap.clampToEdge,\"x\"==m?f.uWrap=c.Atlas.TextureWrap.repeat:\"y\"==m?f.vWrap=c.Atlas.TextureWrap.repeat:\"xy\"==m&&(f.uWrap=f.vWrap=c.Atlas.TextureWrap.repeat),b.load(f,g,this),this.pages.push(f)}else f=null}},c.Atlas.prototype={findRegion:function(a){for(var b=this.regions,c=0,d=b.length;d>c;c++)if(b[c].name==a)return b[c];return null},dispose:function(){for(var a=this.pages,b=0,c=a.length;c>b;b++)this.textureLoader.unload(a[b].rendererObject)},updateUVs:function(a){for(var b=this.regions,c=0,d=b.length;d>c;c++){var e=b[c];e.page==a&&(e.u=e.x/a.width,e.v=e.y/a.height,e.rotate?(e.u2=(e.x+e.height)/a.width,e.v2=(e.y+e.width)/a.height):(e.u2=(e.x+e.width)/a.width,e.v2=(e.y+e.height)/a.height))}}},c.Atlas.Format={alpha:0,intensity:1,luminanceAlpha:2,rgb565:3,rgba4444:4,rgb888:5,rgba8888:6},c.Atlas.TextureFilter={nearest:0,linear:1,mipMap:2,mipMapNearestNearest:3,mipMapLinearNearest:4,mipMapNearestLinear:5,mipMapLinearLinear:6},c.Atlas.TextureWrap={mirroredRepeat:0,clampToEdge:1,repeat:2},c.AtlasPage=function(){},c.AtlasPage.prototype={name:null,format:null,minFilter:null,magFilter:null,uWrap:null,vWrap:null,rendererObject:null,width:0,height:0},c.AtlasRegion=function(){},c.AtlasRegion.prototype={page:null,name:null,x:0,y:0,width:0,height:0,u:0,v:0,u2:0,v2:0,offsetX:0,offsetY:0,originalWidth:0,originalHeight:0,index:0,rotate:!1,splits:null,pads:null},c.AtlasReader=function(a){this.lines=a.split(/\\r\\n|\\r|\\n/)},c.AtlasReader.prototype={index:0,trim:function(a){return a.replace(/^\\s+|\\s+$/g,\"\")},readLine:function(){return this.index>=this.lines.length?null:this.lines[this.index++]},readValue:function(){var a=this.readLine(),b=a.indexOf(\":\");if(-1==b)throw\"Invalid line: \"+a;return this.trim(a.substring(b+1))},readTuple:function(a){var b=this.readLine(),c=b.indexOf(\":\");if(-1==c)throw\"Invalid line: \"+b;for(var d=0,e=c+1;3>d;d++){var f=b.indexOf(\",\",e);if(-1==f)break;a[d]=this.trim(b.substr(e,f-e)),e=f+1}return a[d]=this.trim(b.substring(e)),d+1}},c.AtlasAttachmentLoader=function(a){this.atlas=a},c.AtlasAttachmentLoader.prototype={newRegionAttachment:function(a,b,d){var e=this.atlas.findRegion(d);if(!e)throw\"Region not found in atlas: \"+d+\" (region attachment: \"+b+\")\";var f=new c.RegionAttachment(b);return f.rendererObject=e,f.setUVs(e.u,e.v,e.u2,e.v2,e.rotate),f.regionOffsetX=e.offsetX,f.regionOffsetY=e.offsetY,f.regionWidth=e.width,f.regionHeight=e.height,f.regionOriginalWidth=e.originalWidth,f.regionOriginalHeight=e.originalHeight,f},newMeshAttachment:function(a,b,d){var e=this.atlas.findRegion(d);if(!e)throw\"Region not found in atlas: \"+d+\" (mesh attachment: \"+b+\")\";var f=new c.MeshAttachment(b);return f.rendererObject=e,f.regionU=e.u,f.regionV=e.v,f.regionU2=e.u2,f.regionV2=e.v2,f.regionRotate=e.rotate,f.regionOffsetX=e.offsetX,f.regionOffsetY=e.offsetY,f.regionWidth=e.width,f.regionHeight=e.height,f.regionOriginalWidth=e.originalWidth,f.regionOriginalHeight=e.originalHeight,f},newSkinnedMeshAttachment:function(a,b,d){var e=this.atlas.findRegion(d);if(!e)throw\"Region not found in atlas: \"+d+\" (skinned mesh attachment: \"+b+\")\";var f=new c.SkinnedMeshAttachment(b);return f.rendererObject=e,f.regionU=e.u,f.regionV=e.v,f.regionU2=e.u2,f.regionV2=e.v2,f.regionRotate=e.rotate,f.regionOffsetX=e.offsetX,f.regionOffsetY=e.offsetY,f.regionWidth=e.width,f.regionHeight=e.height,f.regionOriginalWidth=e.originalWidth,f.regionOriginalHeight=e.originalHeight,f},newBoundingBoxAttachment:function(a,b){return new c.BoundingBoxAttachment(b)}},c.SkeletonBounds=function(){this.polygonPool=[],this.polygons=[],this.boundingBoxes=[]},c.SkeletonBounds.prototype={minX:0,minY:0,maxX:0,maxY:0,update:function(a,b){var d=a.slots,e=d.length,f=a.x,g=a.y,h=this.boundingBoxes,i=this.polygonPool,j=this.polygons;h.length=0;for(var k=0,l=j.length;l>k;k++)i.push(j[k]);j.length=0;for(var k=0;e>k;k++){var m=d[k],n=m.attachment;if(n.type==c.AttachmentType.boundingbox){h.push(n);var o,p=i.length;p>0?(o=i[p-1],i.splice(p-1,1)):o=[],j.push(o),o.length=n.vertices.length,n.computeWorldVertices(f,g,m.bone,o)}}b&&this.aabbCompute()},aabbCompute:function(){for(var a=this.polygons,b=Number.MAX_VALUE,c=Number.MAX_VALUE,d=Number.MIN_VALUE,e=Number.MIN_VALUE,f=0,g=a.length;g>f;f++)for(var h=a[f],i=0,j=h.length;j>i;i+=2){var k=h[i],l=h[i+1];b=Math.min(b,k),c=Math.min(c,l),d=Math.max(d,k),e=Math.max(e,l)}this.minX=b,this.minY=c,this.maxX=d,this.maxY=e},aabbContainsPoint:function(a,b){return a>=this.minX&&a=this.minY&&b=a&&e>=c||f>=b&&f>=d||a>=g&&c>=g||b>=h&&d>=h)return!1;var i=(d-b)/(c-a),j=i*(e-a)+b;if(j>f&&h>j)return!0;if(j=i*(g-a)+b,j>f&&h>j)return!0;var k=(f-b)/i+a;return k>e&&g>k?!0:(k=(h-b)/i+a,k>e&&g>k?!0:!1)},aabbIntersectsSkeleton:function(a){return this.minXa.minX&&this.minYa.minY},containsPoint:function(a,b){for(var c=this.polygons,d=0,e=c.length;e>d;d++)if(this.polygonContainsPoint(c[d],a,b))return this.boundingBoxes[d];return null},intersectsSegment:function(a,b,c,d){for(var e=this.polygons,f=0,g=e.length;g>f;f++)if(e[f].intersectsSegment(a,b,c,d))return this.boundingBoxes[f];return null},polygonContainsPoint:function(a,b,c){for(var d=a.length,e=d-2,f=!1,g=0;d>g;g+=2){var h=a[g+1],i=a[e+1];if(c>h&&i>=c||c>i&&h>=c){var j=a[g];j+(c-h)/(i-h)*(a[e]-j)l;l+=2){var m=a[l],n=a[l+1],o=j*n-k*m,p=j-m,q=k-n,r=g*q-h*p,s=(i*p-g*o)/r;if((s>=j&&m>=s||s>=m&&j>=s)&&(s>=b&&d>=s||s>=d&&b>=s)){var t=(i*q-h*o)/r;if((t>=k&&n>=t||t>=n&&k>=t)&&(t>=c&&e>=t||t>=e&&c>=t))return!0}j=m,k=n}return!1},getPolygon:function(a){var b=this.boundingBoxes.indexOf(a);return-1==b?null:this.polygons[b]},getWidth:function(){return this.maxX-this.minX},getHeight:function(){return this.maxY-this.minY}},c.Bone.yDown=!0,b.AnimCache={},b.SpineTextureLoader=function(a,c){b.EventTarget.call(this),this.basePath=a,this.crossorigin=c,this.loadingCount=0},b.SpineTextureLoader.prototype=b.SpineTextureLoader,b.SpineTextureLoader.prototype.load=function(a,c){if(a.rendererObject=b.BaseTexture.fromImage(this.basePath+\"/\"+c,this.crossorigin),!a.rendererObject.hasLoaded){var d=this;++d.loadingCount,a.rendererObject.addEventListener(\"loaded\",function(){--d.loadingCount,d.dispatchEvent({type:\"loadedBaseTexture\",content:d})})}},b.SpineTextureLoader.prototype.unload=function(a){a.destroy(!0)},b.Spine=function(a){if(b.DisplayObjectContainer.call(this),this.spineData=b.AnimCache[a],!this.spineData)throw new Error(\"Spine data must be preloaded using PIXI.SpineLoader or PIXI.AssetLoader: \"+a);this.skeleton=new c.Skeleton(this.spineData),this.skeleton.updateWorldTransform(),this.stateData=new c.AnimationStateData(this.spineData),this.state=new c.AnimationState(this.stateData),this.slotContainers=[];for(var d=0,e=this.skeleton.drawOrder.length;e>d;d++){var f=this.skeleton.drawOrder[d],g=f.attachment,h=new b.DisplayObjectContainer;if(this.slotContainers.push(h),this.addChild(h),g instanceof c.RegionAttachment){var i=g.rendererObject.name,j=this.createSprite(f,g);f.currentSprite=j,f.currentSpriteName=i,h.addChild(j)}else{if(!(g instanceof c.MeshAttachment))continue;var k=this.createMesh(f,g);f.currentMesh=k,f.currentMeshName=g.name,h.addChild(k)}}this.autoUpdate=!0},b.Spine.prototype=Object.create(b.DisplayObjectContainer.prototype),b.Spine.prototype.constructor=b.Spine,Object.defineProperty(b.Spine.prototype,\"autoUpdate\",{get:function(){return this.updateTransform===b.Spine.prototype.autoUpdateTransform},set:function(a){this.updateTransform=a?b.Spine.prototype.autoUpdateTransform:b.DisplayObjectContainer.prototype.updateTransform}}),b.Spine.prototype.update=function(a){this.state.update(a),this.state.apply(this.skeleton),this.skeleton.updateWorldTransform();for(var d=this.skeleton.drawOrder,e=0,f=d.length;f>e;e++){var g=d[e],h=g.attachment,i=this.slotContainers[e];if(h){var j=h.type;if(j===c.AttachmentType.region){if(h.rendererObject&&(!g.currentSpriteName||g.currentSpriteName!==h.name)){var k=h.rendererObject.name;if(void 0!==g.currentSprite&&(g.currentSprite.visible=!1),g.sprites=g.sprites||{},void 0!==g.sprites[k])g.sprites[k].visible=!0;else{var l=this.createSprite(g,h);i.addChild(l)}g.currentSprite=g.sprites[k],g.currentSpriteName=k}var m=g.bone;i.position.x=m.worldX+h.x*m.m00+h.y*m.m01,i.position.y=m.worldY+h.x*m.m10+h.y*m.m11,i.scale.x=m.worldScaleX,i.scale.y=m.worldScaleY,i.rotation=-(g.bone.worldRotation*c.degRad),g.currentSprite.tint=b.rgb2hex([g.r,g.g,g.b])}else{if(j!==c.AttachmentType.skinnedmesh){i.visible=!1;continue}if(!g.currentMeshName||g.currentMeshName!==h.name){var n=h.name;if(void 0!==g.currentMesh&&(g.currentMesh.visible=!1),g.meshes=g.meshes||{},void 0!==g.meshes[n])g.meshes[n].visible=!0;else{var o=this.createMesh(g,h);i.addChild(o)}g.currentMesh=g.meshes[n],g.currentMeshName=n}h.computeWorldVertices(g.bone.skeleton.x,g.bone.skeleton.y,g,g.currentMesh.vertices)}i.visible=!0,i.alpha=g.a}else i.visible=!1}},b.Spine.prototype.autoUpdateTransform=function(){this.lastTime=this.lastTime||Date.now();var a=.001*(Date.now()-this.lastTime);this.lastTime=Date.now(),this.update(a),b.DisplayObjectContainer.prototype.updateTransform.call(this)},b.Spine.prototype.createSprite=function(a,d){var e=d.rendererObject,f=e.page.rendererObject,g=new b.Rectangle(e.x,e.y,e.rotate?e.height:e.width,e.rotate?e.width:e.height),h=new b.Texture(f,g),i=new b.Sprite(h),j=e.rotate?.5*Math.PI:0;return i.scale.set(e.width/e.originalWidth,e.height/e.originalHeight),i.rotation=j-d.rotation*c.degRad,i.anchor.x=i.anchor.y=.5,a.sprites=a.sprites||{},a.sprites[e.name]=i,i},b.Spine.prototype.createMesh=function(a,c){var d=c.rendererObject,e=d.page.rendererObject,f=new b.Texture(e),g=new b.Strip(f);return g.drawMode=b.Strip.DrawModes.TRIANGLES,g.canvasPadding=1.5,g.vertices=new b.Float32Array(c.uvs.length),g.uvs=c.uvs,g.indices=c.triangles,a.meshes=a.meshes||{},a.meshes[c.name]=g,g},b.BaseTextureCache={},b.BaseTextureCacheIdGenerator=0,b.BaseTexture=function(a,c){if(this.resolution=1,this.width=100,this.height=100,this.scaleMode=c||b.scaleModes.DEFAULT,this.hasLoaded=!1,this.source=a,this._UID=b._UID++,this.premultipliedAlpha=!0,this._glTextures=[],this.mipmap=!1,this._dirty=[!0,!0,!0,!0],a){if((this.source.complete||this.source.getContext)&&this.source.width&&this.source.height)this.hasLoaded=!0,this.width=this.source.naturalWidth||this.source.width,this.height=this.source.naturalHeight||this.source.height,this.dirty();else{var d=this;this.source.onload=function(){d.hasLoaded=!0,d.width=d.source.naturalWidth||d.source.width,d.height=d.source.naturalHeight||d.source.height,d.dirty(),d.dispatchEvent({type:\"loaded\",content:d})},this.source.onerror=function(){d.dispatchEvent({type:\"error\",content:d})}}this.imageUrl=null,this._powerOf2=!1}},b.BaseTexture.prototype.constructor=b.BaseTexture,b.EventTarget.mixin(b.BaseTexture.prototype),b.BaseTexture.prototype.destroy=function(){this.imageUrl?(delete b.BaseTextureCache[this.imageUrl],delete b.TextureCache[this.imageUrl],this.imageUrl=null,navigator.isCocoonJS||(this.source.src=\"\")):this.source&&this.source._pixiId&&delete b.BaseTextureCache[this.source._pixiId],this.source=null,this.unloadFromGPU()},b.BaseTexture.prototype.updateSourceImage=function(a){this.hasLoaded=!1,this.source.src=null,this.source.src=a},b.BaseTexture.prototype.dirty=function(){for(var a=0;a=0;a--){var c=this._glTextures[a],d=b.glContexts[a];d&&c&&d.deleteTexture(c)}this._glTextures.length=0,this.dirty()},b.BaseTexture.fromImage=function(a,c,d){var e=b.BaseTextureCache[a];if(void 0===c&&-1===a.indexOf(\"data:\")&&(c=!0),!e){var f=new Image;c&&(f.crossOrigin=\"\"),f.src=a,e=new b.BaseTexture(f,d),e.imageUrl=a,b.BaseTextureCache[a]=e,-1!==a.indexOf(b.RETINA_PREFIX+\".\")&&(e.resolution=2)}return e},b.BaseTexture.fromCanvas=function(a,c){a._pixiId||(a._pixiId=\"canvas_\"+b.TextureCacheIdGenerator++);var d=b.BaseTextureCache[a._pixiId];return d||(d=new b.BaseTexture(a,c),b.BaseTextureCache[a._pixiId]=d),d},b.TextureCache={},b.FrameCache={},b.TextureCacheIdGenerator=0,b.Texture=function(a,c,d,e){this.noFrame=!1,c||(this.noFrame=!0,c=new b.Rectangle(0,0,1,1)),a instanceof b.Texture&&(a=a.baseTexture),this.baseTexture=a,this.frame=c,this.trim=e,this.valid=!1,this.requiresUpdate=!1,this._uvs=null,this.width=0,this.height=0,this.crop=d||new b.Rectangle(0,0,1,1),a.hasLoaded?(this.noFrame&&(c=new b.Rectangle(0,0,a.width,a.height)),this.setFrame(c)):a.addEventListener(\"loaded\",this.onBaseTextureLoaded.bind(this))},b.Texture.prototype.constructor=b.Texture,b.EventTarget.mixin(b.Texture.prototype),b.Texture.prototype.onBaseTextureLoaded=function(){var a=this.baseTexture;a.removeEventListener(\"loaded\",this.onLoaded),this.noFrame&&(this.frame=new b.Rectangle(0,0,a.width,a.height)),this.setFrame(this.frame),this.dispatchEvent({type:\"update\",content:this})},b.Texture.prototype.destroy=function(a){a&&this.baseTexture.destroy(),this.valid=!1},b.Texture.prototype.setFrame=function(a){if(this.noFrame=!1,this.frame=a,this.width=a.width,this.height=a.height,this.crop.x=a.x,this.crop.y=a.y,this.crop.width=a.width,this.crop.height=a.height,!this.trim&&(a.x+a.width>this.baseTexture.width||a.y+a.height>this.baseTexture.height))throw new Error(\"Texture Error: frame does not fit inside the base Texture dimensions \"+this);this.valid=a&&a.width&&a.height&&this.baseTexture.source&&this.baseTexture.hasLoaded,this.trim&&(this.width=this.trim.width,this.height=this.trim.height,this.frame.width=this.trim.width,this.frame.height=this.trim.height),this.valid&&this._updateUvs()},b.Texture.prototype._updateUvs=function(){this._uvs||(this._uvs=new b.TextureUvs);var a=this.crop,c=this.baseTexture.width,d=this.baseTexture.height;this._uvs.x0=a.x/c,this._uvs.y0=a.y/d,this._uvs.x1=(a.x+a.width)/c,this._uvs.y1=a.y/d,this._uvs.x2=(a.x+a.width)/c,this._uvs.y2=(a.y+a.height)/d,this._uvs.x3=a.x/c,this._uvs.y3=(a.y+a.height)/d},b.Texture.fromImage=function(a,c,d){var e=b.TextureCache[a];return e||(e=new b.Texture(b.BaseTexture.fromImage(a,c,d)),b.TextureCache[a]=e),e},b.Texture.fromFrame=function(a){var c=b.TextureCache[a];if(!c)throw new Error('The frameId \"'+a+'\" does not exist in the texture cache ');return c},b.Texture.fromCanvas=function(a,c){var d=b.BaseTexture.fromCanvas(a,c);return new b.Texture(d)},b.Texture.addTextureToCache=function(a,c){b.TextureCache[c]=a},b.Texture.removeTextureFromCache=function(a){var c=b.TextureCache[a];return delete b.TextureCache[a],delete b.BaseTextureCache[a],c},b.TextureUvs=function(){this.x0=0,this.y0=0,this.x1=0,this.y1=0,this.x2=0,this.y2=0,this.x3=0,this.y3=0},b.Texture.emptyTexture=new b.Texture(new b.BaseTexture),b.RenderTexture=function(a,c,d,e,f){if(this.width=a||100,this.height=c||100,this.resolution=f||1,this.frame=new b.Rectangle(0,0,this.width*this.resolution,this.height*this.resolution),this.crop=new b.Rectangle(0,0,this.width*this.resolution,this.height*this.resolution),this.baseTexture=new b.BaseTexture,this.baseTexture.width=this.width*this.resolution,this.baseTexture.height=this.height*this.resolution,this.baseTexture._glTextures=[],this.baseTexture.resolution=this.resolution,this.baseTexture.scaleMode=e||b.scaleModes.DEFAULT,this.baseTexture.hasLoaded=!0,b.Texture.call(this,this.baseTexture,new b.Rectangle(0,0,this.width,this.height)),this.renderer=d||b.defaultRenderer,this.renderer.type===b.WEBGL_RENDERER){var g=this.renderer.gl;this.baseTexture._dirty[g.id]=!1,this.textureBuffer=new b.FilterTexture(g,this.width*this.resolution,this.height*this.resolution,this.baseTexture.scaleMode),this.baseTexture._glTextures[g.id]=this.textureBuffer.texture,this.render=this.renderWebGL,this.projection=new b.Point(.5*this.width,.5*-this.height)}else this.render=this.renderCanvas,this.textureBuffer=new b.CanvasBuffer(this.width*this.resolution,this.height*this.resolution),this.baseTexture.source=this.textureBuffer.canvas;this.valid=!0,this._updateUvs()},b.RenderTexture.prototype=Object.create(b.Texture.prototype),b.RenderTexture.prototype.constructor=b.RenderTexture,b.RenderTexture.prototype.resize=function(a,c,d){(a!==this.width||c!==this.height)&&(this.valid=a>0&&c>0,this.width=this.frame.width=this.crop.width=a,this.height=this.frame.height=this.crop.height=c,d&&(this.baseTexture.width=this.width,this.baseTexture.height=this.height),this.renderer.type===b.WEBGL_RENDERER&&(this.projection.x=this.width/2,this.projection.y=-this.height/2),this.valid&&this.textureBuffer.resize(this.width*this.resolution,this.height*this.resolution))},b.RenderTexture.prototype.clear=function(){this.valid&&(this.renderer.type===b.WEBGL_RENDERER&&this.renderer.gl.bindFramebuffer(this.renderer.gl.FRAMEBUFFER,this.textureBuffer.frameBuffer),this.textureBuffer.clear())},b.RenderTexture.prototype.renderWebGL=function(a,b,c){if(this.valid){var d=a.worldTransform;d.identity(),d.translate(0,2*this.projection.y),b&&d.append(b),d.scale(1,-1),a.worldAlpha=1;for(var e=a.children,f=0,g=e.length;g>f;f++)e[f].updateTransform();var h=this.renderer.gl;h.viewport(0,0,this.width*this.resolution,this.height*this.resolution),h.bindFramebuffer(h.FRAMEBUFFER,this.textureBuffer.frameBuffer),c&&this.textureBuffer.clear(),this.renderer.spriteBatch.dirty=!0,this.renderer.renderDisplayObject(a,this.projection,this.textureBuffer.frameBuffer),this.renderer.spriteBatch.dirty=!0}},b.RenderTexture.prototype.renderCanvas=function(a,b,c){if(this.valid){var d=a.worldTransform;d.identity(),b&&d.append(b),a.worldAlpha=1;for(var e=a.children,f=0,g=e.length;g>f;f++)e[f].updateTransform();c&&this.textureBuffer.clear();var h=this.textureBuffer.context,i=this.renderer.resolution;this.renderer.resolution=this.resolution,this.renderer.renderDisplayObject(a,h),this.renderer.resolution=i}},b.RenderTexture.prototype.getImage=function(){var a=new Image;return a.src=this.getBase64(),a},b.RenderTexture.prototype.getBase64=function(){return this.getCanvas().toDataURL()},b.RenderTexture.prototype.getCanvas=function(){if(this.renderer.type===b.WEBGL_RENDERER){var a=this.renderer.gl,c=this.textureBuffer.width,d=this.textureBuffer.height,e=new Uint8Array(4*c*d);a.bindFramebuffer(a.FRAMEBUFFER,this.textureBuffer.frameBuffer),a.readPixels(0,0,c,d,a.RGBA,a.UNSIGNED_BYTE,e),a.bindFramebuffer(a.FRAMEBUFFER,null);var f=new b.CanvasBuffer(c,d),g=f.context.getImageData(0,0,c,d);return g.data.set(e),f.context.putImageData(g,0,0),f.canvas}return this.textureBuffer.canvas},b.RenderTexture.tempMatrix=new b.Matrix,b.VideoTexture=function(a,c){if(!a)throw new Error(\"No video source element specified.\");(a.readyState===a.HAVE_ENOUGH_DATA||a.readyState===a.HAVE_FUTURE_DATA)&&a.width&&a.height&&(a.complete=!0),b.BaseTexture.call(this,a,c),this.autoUpdate=!1,this.updateBound=this._onUpdate.bind(this),a.complete||(this._onCanPlay=this.onCanPlay.bind(this),a.addEventListener(\"canplay\",this._onCanPlay),a.addEventListener(\"canplaythrough\",this._onCanPlay),a.addEventListener(\"play\",this.onPlayStart.bind(this)),a.addEventListener(\"pause\",this.onPlayStop.bind(this)))},b.VideoTexture.prototype=Object.create(b.BaseTexture.prototype),b.VideoTexture.constructor=b.VideoTexture,b.VideoTexture.prototype._onUpdate=function(){this.autoUpdate&&(window.requestAnimationFrame(this.updateBound),this.dirty())},b.VideoTexture.prototype.onPlayStart=function(){this.autoUpdate||(window.requestAnimationFrame(this.updateBound),this.autoUpdate=!0)},b.VideoTexture.prototype.onPlayStop=function(){this.autoUpdate=!1},b.VideoTexture.prototype.onCanPlay=function(){\"canplaythrough\"===event.type&&(this.hasLoaded=!0,this.source&&(this.source.removeEventListener(\"canplay\",this._onCanPlay),this.source.removeEventListener(\"canplaythrough\",this._onCanPlay),this.width=this.source.videoWidth,this.height=this.source.videoHeight,this.__loaded||(this.__loaded=!0,this.dispatchEvent({type:\"loaded\",content:this}))))},b.VideoTexture.prototype.destroy=function(){this.source&&this.source._pixiId&&(b.BaseTextureCache[this.source._pixiId]=null,delete b.BaseTextureCache[this.source._pixiId],this.source._pixiId=null,delete this.source._pixiId),b.BaseTexture.prototype.destroy.call(this)},b.VideoTexture.baseTextureFromVideo=function(a,c){a._pixiId||(a._pixiId=\"video_\"+b.TextureCacheIdGenerator++);var d=b.BaseTextureCache[a._pixiId];return d||(d=new b.VideoTexture(a,c),b.BaseTextureCache[a._pixiId]=d),d},b.VideoTexture.textureFromVideo=function(a,c){var d=b.VideoTexture.baseTextureFromVideo(a,c);return new b.Texture(d)},b.VideoTexture.fromUrl=function(a,c){var d=document.createElement(\"video\");return d.src=a,d.autoPlay=!0,d.play(),b.VideoTexture.textureFromVideo(d,c)},b.AssetLoader=function(a,c){this.assetURLs=a,this.crossorigin=c,this.loadersByType={jpg:b.ImageLoader,jpeg:b.ImageLoader,png:b.ImageLoader,gif:b.ImageLoader,webp:b.ImageLoader,json:b.JsonLoader,atlas:b.AtlasLoader,anim:b.SpineLoader,xml:b.BitmapFontLoader,fnt:b.BitmapFontLoader}},b.EventTarget.mixin(b.AssetLoader.prototype),b.AssetLoader.prototype.constructor=b.AssetLoader,b.AssetLoader.prototype._getDataType=function(a){var b=\"data:\",c=a.slice(0,b.length).toLowerCase();if(c===b){var d=a.slice(b.length),e=d.indexOf(\",\");if(-1===e)return null;var f=d.slice(0,e).split(\";\")[0];return f&&\"text/plain\"!==f.toLowerCase()?f.split(\"/\").pop().toLowerCase():\"txt\"}return null},b.AssetLoader.prototype.load=function(){function a(a){b.onAssetLoaded(a.data.content)}var b=this;this.loadCount=this.assetURLs.length;for(var c=0;c0?a.addEventListener(\"loadedBaseTexture\",function(a){a.content.content.loadingCount0)if(c%7===1)null!=e&&(this.atlas.frames[d][e.name]=e),e={name:a[g],frame:{}};else{var j=a[g].split(\" \");if(c%7===3)e.frame.x=Number(j[1].replace(\",\",\"\")),e.frame.y=Number(j[2]);else if(c%7===4)e.frame.w=Number(j[1].replace(\",\",\"\")),e.frame.h=Number(j[2]);else if(c%7===5){var k={x:0,y:0,w:Number(j[1].replace(\",\",\"\")),h:Number(j[2])};k.w>e.frame.w||k.h>e.frame.h?(e.trimmed=!0,e.realSize=k):e.trimmed=!1}}c++}if(null!=e&&(this.atlas.frames[d][e.name]=e),this.atlas.meta.image.length>0){for(this.images=[],h=0;hh;h++)for(var i=0;e>i;i++,g++){var j=new b.Texture(this.texture.baseTexture,{x:i*a,y:h*c,width:a,height:c});this.frames.push(j),d&&(b.TextureCache[d+\"-\"+g]=j)}this.load()},b.BitmapFontLoader=function(a,b){this.url=a,this.crossorigin=b,this.baseUrl=a.replace(/[^\\/]*$/,\"\"),this.texture=null},b.BitmapFontLoader.prototype.constructor=b.BitmapFontLoader,b.EventTarget.mixin(b.BitmapFontLoader.prototype),b.BitmapFontLoader.prototype.load=function(){this.ajaxRequest=new b.AjaxRequest,this.ajaxRequest.onreadystatechange=this.onXMLLoaded.bind(this),this.ajaxRequest.open(\"GET\",this.url,!0),this.ajaxRequest.overrideMimeType&&this.ajaxRequest.overrideMimeType(\"application/xml\"),this.ajaxRequest.send(null)},b.BitmapFontLoader.prototype.onXMLLoaded=function(){if(4===this.ajaxRequest.readyState&&(200===this.ajaxRequest.status||-1===window.location.protocol.indexOf(\"http\"))){var a=this.ajaxRequest.responseXML;if(!a||/MSIE 9/i.test(navigator.userAgent)||navigator.isCocoonJS)if(\"function\"==typeof window.DOMParser){var c=new DOMParser;a=c.parseFromString(this.ajaxRequest.responseText,\"text/xml\")}else{var d=document.createElement(\"div\");d.innerHTML=this.ajaxRequest.responseText,a=d}var e=this.baseUrl+a.getElementsByTagName(\"page\")[0].getAttribute(\"file\"),f=new b.ImageLoader(e,this.crossorigin);this.texture=f.texture.baseTexture;var g={},h=a.getElementsByTagName(\"info\")[0],i=a.getElementsByTagName(\"common\")[0];g.font=h.getAttribute(\"face\"),g.size=parseInt(h.getAttribute(\"size\"),10),g.lineHeight=parseInt(i.getAttribute(\"lineHeight\"),10),g.chars={};for(var j=a.getElementsByTagName(\"char\"),k=0;kt?-1:1},Math2.randomInt=function(t,n){return n+=1,Math.floor(Math.random()*(n-t)+t)},Math2.randomBool=function(t){return t=t?t:.5,Math.random() p.x && mousePos.x < p.x + p.size && mousePos.y > p.y && mousePos.y < p.y + p.size) { p.hovered = true; } p.scale.x = p.scale.y = scale = Math.max(Math.min(2.5 - (Math2.distance(p.x, p.y, mousePos.x, mousePos.y) / 160), 160), 1); p.x = p.x + .2 * Math.sin(p.timer * .15) p.y = p.y + .2 * Math.sin(p.timer * .15) p.timer = p.timer + p.v; } window.requestAnimationFrame(update); } init(); update() //# sourceURL=pen.js"}],"posts":[{"title":"SpringBoot+Shiro学习之密码加密和登录失败次数限制","slug":"SpringBoot+Shiro学习之密码加密和登录失败次数限制","date":"2017-03-13T15:53:22.000Z","updated":"2017-03-13T15:53:46.331Z","comments":true,"path":"2017/03/13/SpringBoot+Shiro学习之密码加密和登录失败次数限制/","link":"","permalink":"http://z77z.oschina.io/2017/03/13/SpringBoot+Shiro学习之密码加密和登录失败次数限制/","excerpt":"","text":"这个项目写到现在,基本的雏形出来了,在此感谢一直关注的童鞋,送你们一句最近刚学习的一句鸡汤:念念不忘,必有回响。再贴一张ui图片: z77z后台管理系统 ······················································································································································· 个人博客:http://z77z.oschina.io/ 此项目下载地址:https://git.oschina.net/z77z/springboot_mybatisplus ······················································································································································· 前篇思考问题解决 前篇我们只是完成了同一账户的登录人数限制shiro拦截器的编写,对于手动踢出用户的功能只是说了采用在session域中添加一个key为kickout的布尔值,由之前编写的KickoutSessionControlFilter拦截器来判断是否将用户踢出,还没有说怎么获取当前在线用户的列表的核心代码,下面贴出来: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384/** * <p> * 服务实现类 * </p> * * @author z77z * @since 2017-02-10 */@Servicepublic class SysUserService extends ServiceImpl<SysUserMapper, SysUser> { @Autowired RedisSessionDAO redisSessionDAO; public Page<UserOnlineBo> getPagePlus(FrontPage<UserOnlineBo> frontPage) { // 因为我们是用redis实现了shiro的session的Dao,而且是采用了shiro+redis这个插件 // 所以从spring容器中获取redisSessionDAO // 来获取session列表. Collection<Session> sessions = redisSessionDAO.getActiveSessions(); Iterator<Session> it = sessions.iterator(); List<UserOnlineBo> onlineUserList = new ArrayList<UserOnlineBo>(); Page<UserOnlineBo> pageList = frontPage.getPagePlus(); // 遍历session while (it.hasNext()) { // 这是shiro已经存入session的 // 现在直接取就是了 Session session = it.next(); // 如果被标记为踢出就不显示 Object obj = session.getAttribute(\"kickout\"); if (obj != null) continue; UserOnlineBo onlineUser = getSessionBo(session); onlineUserList.add(onlineUser); } // 再将List<UserOnlineBo>转换成mybatisPlus封装的page对象 int page = frontPage.getPage() - 1; int rows = frontPage.getRows() - 1; int startIndex = page * rows; int endIndex = (page * rows) + rows; int size = onlineUserList.size(); if (endIndex > size) { endIndex = size; } pageList.setRecords(onlineUserList.subList(startIndex, endIndex)); pageList.setTotal(size); return pageList; } //从session中获取UserOnline对象 private UserOnlineBo getSessionBo(Session session){ //获取session登录信息。 Object obj = session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY); if(null == obj){ return null; } //确保是 SimplePrincipalCollection对象。 if(obj instanceof SimplePrincipalCollection){ SimplePrincipalCollection spc = (SimplePrincipalCollection)obj; /** * 获取用户登录的,@link SampleRealm.doGetAuthenticationInfo(...)方法中 * return new SimpleAuthenticationInfo(user,user.getPswd(), getName());的user 对象。 */ obj = spc.getPrimaryPrincipal(); if(null != obj && obj instanceof SysUser){ //存储session + user 综合信息 UserOnlineBo userBo = new UserOnlineBo((SysUser)obj); //最后一次和系统交互的时间 userBo.setLastAccess(session.getLastAccessTime()); //主机的ip地址 userBo.setHost(session.getHost()); //session ID userBo.setSessionId(session.getId().toString()); //session最后一次与系统交互的时间 userBo.setLastLoginTime(session.getLastAccessTime()); //回话到期 ttl(ms) userBo.setTimeout(session.getTimeout()); //session创建时间 userBo.setStartTime(session.getStartTimestamp()); //是否踢出 userBo.setSessionStatus(false); return userBo; } } return null; }} 代码中注释比较完善,也可以去下载源码查看,这样结合看,跟容易理解,不懂的在评论区留言,看见必回! 对Ajax请求的优化:这里有一个前提,我们知道Ajax不能做页面redirect和forward跳转,所以Ajax请求假如没登录,那么这个请求给用户的感觉就是没有任何反应,而用户又不知道用户已经退出了。也就是说在KickoutSessionControlFilter拦截器拦截后,正常如果被踢出,就会跳转到被踢出的提示页面,如果是Ajax请求,给用户的感觉就是没有感觉,核心解决代码如下: 123456789101112131415161718192021222324Map<String, String> resultMap = new HashMap<String, String>();//判断是不是Ajax请求if (\"XMLHttpRequest\".equalsIgnoreCase(((HttpServletRequest) request).getHeader(\"X-Requested-With\"))) { resultMap.put(\"user_status\", \"300\"); resultMap.put(\"message\", \"您已经在其他地方登录,请重新登录!\"); //输出json串 out(response, resultMap);}else{ //重定向 WebUtils.issueRedirect(request, response, kickoutUrl);}private void out(ServletResponse hresponse, Map<String, String> resultMap) throws IOException { try { hresponse.setCharacterEncoding(\"UTF-8\"); PrintWriter out = hresponse.getWriter(); out.println(JSON.toJSONString(resultMap)); out.flush(); out.close(); } catch (Exception e) { System.err.println(\"KickoutSessionFilter.class 输出JSON异常,可以忽略。\"); }} 这是在KickoutSessionControlFilter这个拦截器里面做的修改。 目标: 现在项目里面的密码整个流程都是以明文的方式传递的。这样在实际应用中是很不安全的,京东,开源中国等这些大公司都有泄库事件,这样对用户的隐私造成巨大的影响,所以将密码加密存储传输就非常必要了。 密码重试次数限制,也是出于安全性的考虑。 实现目标一: shiro本身是有对密码加密进行实现的,提供了PasswordService及CredentialsMatcher用于提供加密密码及验证密码服务。这里我觉得这种过于麻烦,大家有需要可以去看这篇博客:shiro加密解密。 我就是自己实现的EDS加密,并且保存的加密明文是采用password+username的方式,减小了密码相同,密文也相同的问题,这里我只是贴一下,EDS的加密解密代码,另外我还改了MyShiroRealm文件,再查数据库的时候加密后再查,而且在创建用户的时候不要忘记的加密存到数据库。这里就补贴代码了。 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980/** * DES加密解密 * * @author z77z * @datetime 2017-3-13 */public class MyDES { /** * DES算法密钥 */ private static final byte[] DES_KEY = { 21, 1, -110, 82, -32, -85, -128, -65 }; /** * 数据加密,算法(DES) * * @param data * 要进行加密的数据 * @return 加密后的数据 */ @SuppressWarnings(\"restriction\") public static String encryptBasedDes(String data) { String encryptedData = null; try { // DES算法要求有一个可信任的随机数源 SecureRandom sr = new SecureRandom(); DESKeySpec deskey = new DESKeySpec(DES_KEY); // 创建一个密匙工厂,然后用它把DESKeySpec转换成一个SecretKey对象 SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(\"DES\"); SecretKey key = keyFactory.generateSecret(deskey); // 加密对象 Cipher cipher = Cipher.getInstance(\"DES\"); cipher.init(Cipher.ENCRYPT_MODE, key, sr); // 加密,并把字节数组编码成字符串 encryptedData = new sun.misc.BASE64Encoder().encode(cipher.doFinal(data.getBytes())); } catch (Exception e) { // log.error(\"加密错误,错误信息:\", e); throw new RuntimeException(\"加密错误,错误信息:\", e); } return encryptedData; } /** * 数据解密,算法(DES) * * @param cryptData * 加密数据 * @return 解密后的数据 */ @SuppressWarnings(\"restriction\") public static String decryptBasedDes(String cryptData) { String decryptedData = null; try { // DES算法要求有一个可信任的随机数源 SecureRandom sr = new SecureRandom(); DESKeySpec deskey = new DESKeySpec(DES_KEY); // 创建一个密匙工厂,然后用它把DESKeySpec转换成一个SecretKey对象 SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(\"DES\"); SecretKey key = keyFactory.generateSecret(deskey); // 解密对象 Cipher cipher = Cipher.getInstance(\"DES\"); cipher.init(Cipher.DECRYPT_MODE, key, sr); // 把字符串解码为字节数组,并解密 decryptedData = new String(cipher.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(cryptData))); } catch (Exception e) { // log.error(\"解密错误,错误信息:\", e); throw new RuntimeException(\"解密错误,错误信息:\", e); } return decryptedData; } public static void main(String[] args) { String str = \"123456\"; // DES数据加密 String s1 = encryptBasedDes(str); System.out.println(s1); // DES数据解密 String s2 = decryptBasedDes(s1); System.err.println(s2); }} 实现目标二 如在1个小时内密码最多重试5次,如果尝试次数超过5次就锁定1小时,1小时后可再次重试,如果还是重试失败,可以锁定如1天,以此类推,防止密码被暴力破解。我们使用redis数据库来保存当前用户登录次数,也就是执行身份认证方法:MyShiroRealm.doGetAuthenticationInfo()的次数,如果登录成功就清空计数。超过就返回相应错误信息。根据这个逻辑,修改MyShiroRealm.java如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657/*** 认证信息.(身份验证) : Authentication 是用来验证用户身份 * * @param token * @return * @throws AuthenticationException */@Overrideprotected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken authcToken) throws AuthenticationException { System.out.println(\"身份认证方法:MyShiroRealm.doGetAuthenticationInfo()\"); UsernamePasswordToken token = (UsernamePasswordToken) authcToken; String name = token.getUsername(); String password = String.valueOf(token.getPassword()); //访问一次,计数一次 ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue(); opsForValue.increment(SHIRO_LOGIN_COUNT+name, 1); //计数大于5时,设置用户被锁定一小时 if(Integer.parseInt(opsForValue.get(SHIRO_LOGIN_COUNT+name))>=5){ opsForValue.set(SHIRO_IS_LOCK+name, \"LOCK\"); stringRedisTemplate.expire(SHIRO_IS_LOCK+name, 1, TimeUnit.HOURS); } if (\"LOCK\".equals(opsForValue.get(SHIRO_IS_LOCK+name))){ throw new DisabledAccountException(\"由于密码输入错误次数大于5次,帐号已经禁止登录!\"); } Map<String, Object> map = new HashMap<String, Object>(); map.put(\"nickname\", name); //密码进行加密处理 明文为 password+name String paw = password+name; String pawDES = MyDES.encryptBasedDes(paw); map.put(\"pswd\", pawDES); SysUser user = null; // 从数据库获取对应用户名密码的用户 List<SysUser> userList = sysUserService.selectByMap(map); if(userList.size()!=0){ user = userList.get(0); } if (null == user) { throw new AccountException(\"帐号或密码不正确!\"); }else if(user.getStatus()==0){ /** * 如果用户的status为禁用。那么就抛出<code>DisabledAccountException</code> */ throw new DisabledAccountException(\"此帐号已经设置为禁止登录!\"); }else{ //登录成功 //更新登录时间 last login time user.setLastLoginTime(new Date()); sysUserService.updateById(user); //清空登录计数 opsForValue.set(SHIRO_LOGIN_COUNT+name, \"0\"); } return new SimpleAuthenticationInfo(user, password, getName());}","categories":[],"tags":[{"name":"springboot","slug":"springboot","permalink":"http://z77z.oschina.io/tags/springboot/"},{"name":"redis","slug":"redis","permalink":"http://z77z.oschina.io/tags/redis/"},{"name":"shiro","slug":"shiro","permalink":"http://z77z.oschina.io/tags/shiro/"}]},{"title":"SpringBoot+Shiro学习之自定义拦截器管理在线用户(踢出用户)","slug":"SpringBoot+Shiro学习之自定义拦截器管理在线用户(踢出用户)","date":"2017-03-05T09:27:14.000Z","updated":"2017-03-05T09:27:36.077Z","comments":true,"path":"2017/03/05/SpringBoot+Shiro学习之自定义拦截器管理在线用户(踢出用户)/","link":"","permalink":"http://z77z.oschina.io/2017/03/05/SpringBoot+Shiro学习之自定义拦截器管理在线用户(踢出用户)/","excerpt":"","text":"应用场景 我们经常会有用到,当A 用户在北京登录 ,然后A用户在天津再登录 ,要踢出北京登录的状态。如果用户在北京重新登录,那么又要踢出天津的用户,这样反复。又或是需要限制同一用户的同时在线数量,超出限制后,踢出最先登录的或是踢出最后登录的。 第一个场景踢出用户是由用户触发的,有时候需要手动将某个在线用户踢出,也就是对当前在线用户的列表进行管理。 ·························································································································································个人博客:http://z77z.oschina.io/ 此项目下载地址:https://git.oschina.net/z77z/springboot_mybatisplus························································································································································ 实现思路spring security就直接提供了相应的功能;Shiro的话没有提供默认实现,不过可以很容易的在Shiro中加入这个功能。那就是使用shiro强大的自定义访问控制拦截器:AccessControlFilter,集成这个接口后要实现下面这三个方法。 12345abstract boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception; boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception; abstract boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception; isAccessAllowed:表示是否允许访问;mappedValue就是[urls]配置中拦截器参数部分,如果允许访问返回true,否则false; onAccessDenied:表示当访问拒绝时是否已经处理了;如果返回true表示需要继续处理;如果返回false表示该拦截器实例已经处理了,将直接返回即可。 onPreHandle:会自动调用这两个方法决定是否继续处理; 另外AccessControlFilter还提供了如下方法用于处理如登录成功后/重定向到上一个请求: 1234567void setLoginUrl(String loginUrl) //身份验证时使用,默认/login.jsp String getLoginUrl() Subject getSubject(ServletRequest request, ServletResponse response) //获取Subject实例 boolean isLoginRequest(ServletRequest request, ServletResponse response)//当前请求是否是登录请求 void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException //将当前请求保存起来并重定向到登录页面 void saveRequest(ServletRequest request) //将请求保存起来,如登录成功后再重定向回该请求 void redirectToLogin(ServletRequest request, ServletResponse response) //重定向到登录页面 比如基于表单的身份验证就需要使用这些功能。 到此基本的拦截器就完事了,如果我们想进行访问的控制就可以继承AccessControlFilter;如果我们要添加一些通用数据我们可以直接继承PathMatchingFilter。 下面就是我实现的访问控制拦截器:KickoutSessionControlFilter: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107/** * @author 作者 z77z * @date 创建时间:2017年3月5日 下午1:16:38 * 思路: * 1.读取当前登录用户名,获取在缓存中的sessionId队列 * 2.判断队列的长度,大于最大登录限制的时候,按踢出规则 * 将之前的sessionId中的session域中存入kickout:true,并更新队列缓存 * 3.判断当前登录的session域中的kickout如果为true, * 想将其做退出登录处理,然后再重定向到踢出登录提示页面 */public class KickoutSessionControlFilter extends AccessControlFilter { private String kickoutUrl; //踢出后到的地址 private boolean kickoutAfter = false; //踢出之前登录的/之后登录的用户 默认踢出之前登录的用户 private int maxSession = 1; //同一个帐号最大会话数 默认1 private SessionManager sessionManager; private Cache<String, Deque<Serializable>> cache; public void setKickoutUrl(String kickoutUrl) { this.kickoutUrl = kickoutUrl; } public void setKickoutAfter(boolean kickoutAfter) { this.kickoutAfter = kickoutAfter; } public void setMaxSession(int maxSession) { this.maxSession = maxSession; } public void setSessionManager(SessionManager sessionManager) { this.sessionManager = sessionManager; } //设置Cache的key的前缀 public void setCacheManager(CacheManager cacheManager) { this.cache = cacheManager.getCache(\"shiro_redis_cache\"); } @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { return false; } @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { Subject subject = getSubject(request, response); if(!subject.isAuthenticated() && !subject.isRemembered()) { //如果没有登录,直接进行之后的流程 return true; } Session session = subject.getSession(); SysUser user = (SysUser) subject.getPrincipal(); String username = user.getNickname(); Serializable sessionId = session.getId(); //读取缓存 没有就存入 Deque<Serializable> deque = cache.get(username); //如果队列里没有此sessionId,且用户没有被踢出;放入队列 if(!deque.contains(sessionId) && session.getAttribute(\"kickout\") == null) { //将sessionId存入队列 deque.push(sessionId); //将用户的sessionId队列缓存 cache.put(username, deque); } //如果队列里的sessionId数超出最大会话数,开始踢人 while(deque.size() > maxSession) { Serializable kickoutSessionId = null; if(kickoutAfter) { //如果踢出后者 kickoutSessionId = deque.removeFirst(); } else { //否则踢出前者 kickoutSessionId = deque.removeLast(); } //踢出后再更新下缓存队列 cache.put(username, deque); try { //获取被踢出的sessionId的session对象 Session kickoutSession = sessionManager.getSession(new DefaultSessionKey(kickoutSessionId)); if(kickoutSession != null) { //设置会话的kickout属性表示踢出了 kickoutSession.setAttribute(\"kickout\", true); } } catch (Exception e) {//ignore exception } } //如果被踢出了,直接退出,重定向到踢出后的地址 if ((Boolean)session.getAttribute(\"kickout\")!=null&&(Boolean)session.getAttribute(\"kickout\") == true) { //会话被踢出了 try { //退出登录 subject.logout(); } catch (Exception e) { //ignore } saveRequest(request); //重定向 WebUtils.issueRedirect(request, response, kickoutUrl); return false; } return true; }} 将这个自定义的拦截器配置在ShiroConfig.java文件中: 1234567891011121314151617181920/** * 限制同一账号登录同时登录人数控制 * @return */ public KickoutSessionControlFilter kickoutSessionControlFilter(){ KickoutSessionControlFilter kickoutSessionControlFilter = new KickoutSessionControlFilter(); //使用cacheManager获取相应的cache来缓存用户登录的会话;用于保存用户—会话之间的关系的; //这里我们还是用之前shiro使用的redisManager()实现的cacheManager()缓存管理 //也可以重新另写一个,重新配置缓存时间之类的自定义缓存属性 kickoutSessionControlFilter.setCacheManager(cacheManager()); //用于根据会话ID,获取会话进行踢出操作的; kickoutSessionControlFilter.setSessionManager(sessionManager()); //是否踢出后来登录的,默认是false;即后者登录的用户踢出前者登录的用户;踢出顺序。 kickoutSessionControlFilter.setKickoutAfter(false); //同一个用户最大的会话数,默认1;比如2的意思是同一个用户允许最多同时两个人登录; kickoutSessionControlFilter.setMaxSession(1); //被踢出后重定向到的地址; kickoutSessionControlFilter.setKickoutUrl(\"/kickout\"); return kickoutSessionControlFilter; } 将这个kickoutSessionControlFilter()注入到shiroFilterFactoryBean中: 12345//自定义拦截器Map<String, Filter> filtersMap = new LinkedHashMap<String, Filter>();//限制同一帐号同时在线的个数。filtersMap.put(\"kickout\", kickoutSessionControlFilter());shiroFilterFactoryBean.setFilters(filtersMap); 由于我们链接权限的控制是动态存在数据库中的,这个可以去看我之前动态权限控制的博文,所以我们还要在数据库中修改链接的权限,将kickout这个自定义的权限配置在对应的链接上。如下图: 权限表 还要编写对应的被踢出的跳转页面: 1234567891011121314151617181920212223242526<%@ page language=\"java\" contentType=\"text/html; charset=UTF-8\" pageEncoding=\"UTF-8\"%><% String path = request.getContextPath(); String basePath = request.getScheme() + \"://\" + request.getServerName() + \":\" + request.getServerPort() + path;%><!DOCTYPE html><html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"><script type=\"text/javascript\" src=\"<%=basePath%>/static/js/jquery-1.11.3.js\"></script><title>被踢出</title></head><body>被踢出 或则在另一地方登录,或已经达到此账号登录上限被挤掉。<input type=\"button\" id=\"login\" value=\"重新登录\" /></body><script type=\"text/javascript\">$(\"#login\").click(function(){ window.open(\"<%=basePath%>/login\"); });</script></html> 到此,第一个场景就实现了,写到这里实际第二个场景的实现思路已经就很明显了,可以通过sessionDAO获取到全部的shiro会话List,然后显示在前端页面,踢出对应用户就可以使用在对应sessionId的session域中设置key为kickout的值为true,上面的KickoutSessionControlFilter就会判断session域中的kickout值,做响应的处理。这里我就先不上代码了,大家可以自己试一试。之后再把代码同步到我的码云上,供大家学习交流。 处理了这个需求后,我发现一个问题,这里有一个前提,我们知道Ajax不能做页面redirect和forward跳转,所以Ajax请求假如没登录,那么这个请求给用户的感觉就是没有任何反应,而用户又不知道用户已经退出了。这个就要对ajax请求做相应的优化,我已经有解决思路了,大家也可以思考下,我也会在下一博提供代码。 还有我接下来会对之前的前端页面进行完善,比如下面是我更新的登录页面: 登录页面 已经更新到我的码云上面。","categories":[],"tags":[{"name":"springboot","slug":"springboot","permalink":"http://z77z.oschina.io/tags/springboot/"},{"name":"redis","slug":"redis","permalink":"http://z77z.oschina.io/tags/redis/"},{"name":"shiro","slug":"shiro","permalink":"http://z77z.oschina.io/tags/shiro/"}]},{"title":"SpringBoot+Shiro学习之“记住我”和“GIF验证码”功能的实现","slug":"SpringBoot+Shiro学习之“记住我”和“GIF验证码”功能的实现","date":"2017-02-26T10:43:44.000Z","updated":"2017-02-26T11:21:07.645Z","comments":true,"path":"2017/02/26/SpringBoot+Shiro学习之“记住我”和“GIF验证码”功能的实现/","link":"","permalink":"http://z77z.oschina.io/2017/02/26/SpringBoot+Shiro学习之“记住我”和“GIF验证码”功能的实现/","excerpt":"","text":"学习目标如标题有如下两个功能实现: 记住我的功能:通过设置key为“rememberMe”的cookie保存在客户端来完成记住我的功能,下次用户访问指定页面时就不会重新登录,一直到cookie过期后才会重新登录。 GIF格式验证码: ,这个要感谢sojson的博主对这个GIF验证码插件的实现。 个人博客:http://z77z.oschina.io/ 此项目下载地址:https://git.oschina.net/z77z/springboot_mybatisplus 记住我 ShiroConfig的配置: 在ShiroConfig.java中添加如下方法: 1234567891011121314151617181920212223/** * cookie对象; * @return */public SimpleCookie rememberMeCookie(){ //这个参数是cookie的名称,对应前端的checkbox的name = rememberMe SimpleCookie simpleCookie = new SimpleCookie(\"rememberMe\"); //<!-- 记住我cookie生效时间30天 ,单位秒;--> simpleCookie.setMaxAge(2592000); return simpleCookie;}/** * cookie管理对象;记住我功能 * @return */public CookieRememberMeManager rememberMeManager(){ CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager(); cookieRememberMeManager.setCookie(rememberMeCookie()); //rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位) cookieRememberMeManager.setCipherKey(Base64.decode(\"3AvVhmFLUs0KTA3Kprsdag==\")); return cookieRememberMeManager;} rememberMeCookie()方法是设置Cookie的生成模版,比如cookie的name,cookie的有效时间等等。rememberMeManager()方法是生成rememberMe管理器,而且要将这个rememberMe管理器设置到securityManager中,如下: 12345678910111213@Beanpublic SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 设置realm. securityManager.setRealm(myShiroRealm()); // 自定义缓存实现 使用redis securityManager.setCacheManager(cacheManager()); // 自定义session管理 使用redis securityManager.setSessionManager(SessionManager()); //注入记住我管理器; securityManager.setRememberMeManager(rememberMeManager()); return securityManager;} 其实上面的步骤,也就是rememberMe管理器可以不用配置,shiro会使用默认的配置,之所以要配置的目的是为了能够在实际业务环境中自定义其中的参数。 登录controller的改造 12345678910111213141516@RequestMapping(value=\"ajaxLogin\",method=RequestMethod.POST)@ResponseBodypublic Map<String,Object> submitLogin(String username, String password,Boolean rememberMe,Model model) { Map<String, Object> resultMap = new LinkedHashMap<String, Object>(); try { UsernamePasswordToken token = new UsernamePasswordToken(username, password,rememberMe); SecurityUtils.getSubject().login(token); resultMap.put(\"status\", 200); resultMap.put(\"message\", \"登录成功\"); } catch (Exception e) { resultMap.put(\"status\", 500); resultMap.put(\"message\", e.getMessage()); } return resultMap;} 之前我是将shiro已经实现的UsernamePasswordToken类再封装了一层,最后发现没有必要,直接使用shiro提供的UsernamePasswordToken的类,其中有一个构造函数需要传rememberMe这个参数,也就是shiro为我们已经实现好了,推荐大家去看下UsernamePasswordToken这个类的源码。 jsp页面的改造 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354<!DOCTYPE html><%@ page language=\"java\" contentType=\"text/html; charset=utf-8\" pageEncoding=\"utf-8\"%><% String path = request.getContextPath(); String basePath = request.getScheme() + \"://\" + request.getServerName() + \":\" + request.getServerPort() + path;%><html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"><script type=\"text/javascript\" src=\"<%=basePath%>/static/js/jquery-1.11.3.js\"></script><title>登录</title></head><body> 错误信息: <h4 id=\"erro\"></h4> <form> <p> 账号:<input type=\"text\" name=\"username\" id=\"username\" value=\"admin\" /> </p> <p> 密码:<input type=\"text\" name=\"password\" id=\"password\" value=\"123\" /> </p> <P><input type=\"checkbox\" name=\"rememberMe\" id=\"rememberMe\" />记住我</P> <p> <input type=\"button\" id=\"ajaxLogin\" value=\"登录\" /> </p> </form></body><script>$(function(){ $(\"#ajaxLogin\").click(function() { var username = $(\"#username\").val(); var password = $(\"#password\").val(); var rememberMe =$('#rememberMe').is(':checked'); $.post(\"/ajaxLogin\", { \"username\" : username, \"password\" : password, \"rememberMe\" : rememberMe }, function(result) { if (result.status == 200) { location.href = \"/index\"; } else { $(\"#erro\").html(result.message); } }); });});</script></html> 添加一个记住我的单选框,来控制是否需要记住我。 修改权限配置,修改sys_permission_init表 链接权限控制表 因为getGifCode是获取验证码的链接,所以要配置为anon,不需要权限验证。user权限是配置记住我或认证通过可以访问,所以将/**链接设置为user权限,就可以实现记住我的功能。 注意权限添加的排序。 GIF验证码 编写一个获取GIF验证码图片的controller: 12345678910111213141516171819202122232425/** * 获取验证码(Gif版本) * @param response */@RequestMapping(value=\"getGifCode\",method=RequestMethod.GET)public void getGifCode(HttpServletResponse response,HttpServletRequest request){ try { response.setHeader(\"Pragma\", \"No-cache\"); response.setHeader(\"Cache-Control\", \"no-cache\"); response.setDateHeader(\"Expires\", 0); response.setContentType(\"image/gif\"); /** * gif格式动画验证码 * 宽,高,位数。 */ Captcha captcha = new GifCaptcha(146,33,4); //输出 captcha.out(response.getOutputStream()); HttpSession session = request.getSession(true); //存入Session session.setAttribute(\"_code\",captcha.text().toLowerCase()); } catch (Exception e) { System.err.println(\"获取验证码异常:\"+e.getMessage()); }} 生成验证码后,将图片返回到页面,将字符串保存在当前会话的session域中。 这个GIF验证码的生成插件源码在我的项目io.z77z.vcode这个包下面,大家需要的话可以在我的码云上去下载。 改造登录controller: 1234567891011121314151617181920212223242526272829303132333435363738394041/** * ajax登录请求 * @param username * @param password * @return */@RequestMapping(value=\"ajaxLogin\",method=RequestMethod.POST)@ResponseBodypublic Map<String,Object> submitLogin(String username, String password,String vcode,Boolean rememberMe,Model model) { Map<String, Object> resultMap = new LinkedHashMap<String, Object>(); if(vcode==null||vcode==\"\"){ resultMap.put(\"status\", 500); resultMap.put(\"message\", \"验证码不能为空!\"); return resultMap; } Session session = SecurityUtils.getSubject().getSession(); //转化成小写字母 vcode = vcode.toLowerCase(); String v = (String) session.getAttribute(\"_code\"); //还可以读取一次后把验证码清空,这样每次登录都必须获取验证码 //session.removeAttribute(\"_come\"); if(!vcode.equals(v)){ resultMap.put(\"status\", 500); resultMap.put(\"message\", \"验证码错误!\"); return resultMap; } try { UsernamePasswordToken token = new UsernamePasswordToken(username, password,rememberMe); SecurityUtils.getSubject().login(token); resultMap.put(\"status\", 200); resultMap.put(\"message\", \"登录成功\"); } catch (Exception e) { resultMap.put(\"status\", 500); resultMap.put(\"message\", e.getMessage()); } return resultMap;} 登录的时候判断前台传入的验证码和session中的是否一致。 前端jsp页面改造 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263<!DOCTYPE html><%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%><% String path = request.getContextPath(); String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path;%><html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><script type="text/javascript" src="<%=basePath%>/static/js/jquery-1.11.3.js"></script><title>登录</title></head><body> 错误信息: <h4 id="erro"></h4> <form> <p> 账号:<input type="text" name="username" id="username" value="admin" /> </p> <p> 密码:<input type="text" name="password" id="password" value="123" /> </p> <p> 验证码:<input type="text" name="vcode" id="vcode"/> </p> <p> <img alt="验证码" src="/getGifCode"> </p> <P><input type="checkbox" name="rememberMe" id="rememberMe" />记住我</P> <p> <input type="button" id="ajaxLogin" value="登录" /> </p> </form></body><script>$(function(){ $("#ajaxLogin").click(function() { var username = $("#username").val(); var password = $("#password").val(); var vcode = $("#vcode").val(); var rememberMe =$('#rememberMe').is(':checked'); $.post("/ajaxLogin", { "username" : username, "password" : password, "vcode" : vcode, "rememberMe" : rememberMe }, function(result) { if (result.status == 200) { location.href = "/index"; } else { $("#erro").html(result.message); } }); });});</script></html> 总结:到此,我们集成shiro和redis,学习了一下功能的实现: 用户必须要登陆之后才能访问定义链接,否则跳转到登录页面,被禁用户不能登录。并且对一些敏感操作链接设置权限,只有满足权限的才可以访问。 每个链接的权限信息保存在数据库,可以动态进行设置,并且热加载权限。 使用redis对shiro的用户信息进行缓存,不用每次都去执行MyShiroRealm.doGetAuthorizationInfo()权限认证方法。 之前有很多同学下载我的项目时,运行会报错,那是因为最近都在不断修改提交,有可能会出现版本问题,现在我在我的码云上面创建了stable_version分支,都是可以跑起来的。sqltable放在resource目录下面。 “记住我”的功能,利用cookie。 GIF验证码的生成,在登陆时进行验证码的校验。利用session。 下一博,我应该会写对在线用户的管理,踢出登录的功能学习记录。 香蕉硬币点赞走一波啦。。。。。。","categories":[],"tags":[{"name":"springboot","slug":"springboot","permalink":"http://z77z.oschina.io/tags/springboot/"},{"name":"shiro","slug":"shiro","permalink":"http://z77z.oschina.io/tags/shiro/"},{"name":"GIF验证码","slug":"GIF验证码","permalink":"http://z77z.oschina.io/tags/GIF验证码/"}]},{"title":"SpringBoot+Shiro学习之数据库动态权限管理和Redis缓存","slug":"SpringBoot+Shiro学习之数据库动态权限管理和Redis缓存","date":"2017-02-17T14:12:58.000Z","updated":"2017-02-26T10:42:40.106Z","comments":true,"path":"2017/02/17/SpringBoot+Shiro学习之数据库动态权限管理和Redis缓存/","link":"","permalink":"http://z77z.oschina.io/2017/02/17/SpringBoot+Shiro学习之数据库动态权限管理和Redis缓存/","excerpt":"","text":"发现问题,需找解决思路。之前我们整合Shiro,完成了登录认证和权限管理的实现,登录认证没什么说的,需要实现AuthorizingRealm中的doGetAuthenticationInfo方法进行认证,但是我们在实现doGetAuthorizationInfo权限控制这个方法的时候发现以下两个问题: 第一个问题:我们在ShiroConfig中配置链接权限的时候,每次只要有一个新的链接,或则权限需要改动,都要在ShiroConfig.java中进行权限的修改。而且改动后还需要重新启动程序新的权限才会生效,很麻烦。解决办法就是将这些链接的权限存入数据库,在前端可以提供增删改查的功能,在配置文件中编写权限的时候从数据库读取,当权限发生变更的时候利用ShiroFilterFactoryBean的清空功能,先clear,再set。这样就可以做到到动态的管理权限了。 第二个问题:每次在访问设置了权限的页面时,都会去执行doGetAuthorizationInfo方法来判断当前用户是否具备访问权限,由于在实际情况中,权限是不会经常改变的。解决办法就是进行缓存处理。 个人博客:http://z77z.oschina.io/ 此项目下载地址:https://git.oschina.net/z77z/springboot_mybatisplus 第一个问题解决步骤建立数据库 我们从ShiroConfig中的filterChainDefinitionMap.put("/add", "perms[权限添加]"); 配置可以看出,我们需要存储链接,和链接需要具备的权限这两个关键字段。还有这个权限的读取是有顺序的,所以还要进行排序控制,所以我新建表为: 1234567891011-- ------------------------------ Table structure for sys_permission_init-- ----------------------------DROP TABLE IF EXISTS `sys_permission_init`;CREATE TABLE `sys_permission_init` ( `id` varchar(255) NOT NULL, `url` varchar(255) DEFAULT NULL COMMENT '链接地址', `permission_init` varchar(255) DEFAULT NULL COMMENT '需要具备的权限', `sort` int(50) DEFAULT NULL COMMENT '排序', PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8; 当然可以按实际情况进行表的设计,这里只做简单学习。 改造ShiroConfig.java 改造前: 123456789101112131415161718192021222324252627282930313233@Beanpublic ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 必须设置 SecurityManager shiroFilterFactoryBean.setSecurityManager(securityManager); // 如果不设置默认会自动寻找Web工程根目录下的\"/login.jsp\"页面 shiroFilterFactoryBean.setLoginUrl(\"/login\"); // 登录成功后要跳转的链接 shiroFilterFactoryBean.setSuccessUrl(\"/index\"); // 未授权界面; shiroFilterFactoryBean.setUnauthorizedUrl(\"/403\"); // 拦截器. Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); // 配置不会被拦截的链接 顺序判断 filterChainDefinitionMap.put(\"/static/**\", \"anon\"); filterChainDefinitionMap.put(\"/ajaxLogin\", \"anon\"); // 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了 filterChainDefinitionMap.put(\"/logout\", \"logout\"); filterChainDefinitionMap.put(\"/add\", \"perms[权限添加]\"); // <!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了; // <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问--> filterChainDefinitionMap.put(\"/**\", \"authc\"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); System.out.println(\"Shiro拦截器工厂类注入成功\"); return shiroFilterFactoryBean;} 改造后: 1234567891011121314151617181920212223242526272829@Beanpublic ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 必须设置 SecurityManager shiroFilterFactoryBean.setSecurityManager(securityManager); // 如果不设置默认会自动寻找Web工程根目录下的\"/login.jsp\"页面 shiroFilterFactoryBean.setLoginUrl(\"/login\"); // 登录成功后要跳转的链接 shiroFilterFactoryBean.setSuccessUrl(\"/index\"); // 未授权界面; shiroFilterFactoryBean.setUnauthorizedUrl(\"/403\"); // 权限控制map. Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); //从数据库获取 List<SysPermissionInit> list = sysPermissionInitService.selectAll(); for (SysPermissionInit sysPermissionInit : list) { filterChainDefinitionMap.put(sysPermissionInit.getUrl(), sysPermissionInit.getPermissionInit()); } shiroFilterFactoryBean .setFilterChainDefinitionMap(filterChainDefinitionMap); System.out.println(\"Shiro拦截器工厂类注入成功\"); return shiroFilterFactoryBean;} 这里的selectAll()就是从数据库查询之前创建的权限管理列表,这里就不贴具体的查询代码了。 添加权限 在数据库中添加权限如下图: 这里写图片描述 现在启动程序,在控制台可以发现启动的时候程序在数据库查询了权限的列表信息。做到这步之后还没有达到动态的目的,比如现在到数据库手动修改/add链接的权限,这时不重启程序,权限是不会修改的。 动态更改权限实现 ShiroService.java: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970/** * * @author 作者: z77z * @date 创建时间:2017年2月15日 下午4:16:07 */@Servicepublic class ShiroService { @Autowired ShiroFilterFactoryBean shiroFilterFactoryBean; @Autowired SysPermissionInitService sysPermissionInitService; /** * 初始化权限 */ public Map<String, String> loadFilterChainDefinitions() { // 权限控制map.从数据库获取 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); List<SysPermissionInit> list = sysPermissionInitService.selectAll(); for (SysPermissionInit sysPermissionInit : list) { filterChainDefinitionMap.put(sysPermissionInit.getUrl(), sysPermissionInit.getPermissionInit()); } return filterChainDefinitionMap; } /** * 重新加载权限 */ public void updatePermission() { synchronized (shiroFilterFactoryBean) { AbstractShiroFilter shiroFilter = null; try { shiroFilter = (AbstractShiroFilter) shiroFilterFactoryBean .getObject(); } catch (Exception e) { throw new RuntimeException( \"get ShiroFilter from shiroFilterFactoryBean error!\"); } PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) shiroFilter .getFilterChainResolver(); DefaultFilterChainManager manager = (DefaultFilterChainManager) filterChainResolver .getFilterChainManager(); // 清空老的权限控制 manager.getFilterChains().clear(); shiroFilterFactoryBean.getFilterChainDefinitionMap().clear(); shiroFilterFactoryBean .setFilterChainDefinitionMap(loadFilterChainDefinitions()); // 重新构建生成 Map<String, String> chains = shiroFilterFactoryBean .getFilterChainDefinitionMap(); for (Map.Entry<String, String> entry : chains.entrySet()) { String url = entry.getKey(); String chainDefinition = entry.getValue().trim() .replace(\" \", \"\"); manager.createChain(url, chainDefinition); } System.out.println(\"更新权限成功!!\"); } }} 这样,可以在修改权限之后,执行updatePermission()这个方法,权限就会先被clear,然后重新查询权限列表后再set。动态修改就实现了! 注意:在本学习项目里面,我在设置登录用户的权限的时候是写死了的,所以每个登录用户权限都是一样的,实际开发中在MyShiroRealm文件中设置登录用户的权限是从数据库获取的。还有在实际开发中sys_permission_init权限管理这种表是会在前端提供增删改查功能的,我学习的时候是直接在数据库手动修改。说到底,本人很懒! 第二个问题的解决步骤我们知道Shiro 提供了一系列让我们自己实现的接口,包括org.apache.shiro.cache.CacheManager 、org.apache.shiro.cache.Cache 等接口。那么我们要对这些做实现,就实现了 Shiro 对 Session 和用户认证信息、用户缓存信息等的缓存,存储。我们可以用缓存,如 Redis 、 memcache 、 EHCache 等,甚至我们可以用数据库,如 Oracle 、 Mysql 等,都可以,只有效率的快慢问题,功能都可以达到。 那么我的教程是采用了 Redis ,而且是用了Jedis 。Jedis 可以实现pool 和hash 的集群Redis 。 本来我想是在网上学习学习,自己实现redis的集成。最后发现已经有大神已经做了这个插件,对shiro提供的CacheManager,Cache ,这些接口使用redis都有了很好的实现。我就不需要再费心学习了,我们就直接拿来用。 pom.xml依赖添加 123456<!-- shiro+redis缓存插件 --><dependency> <groupId>org.crazycake</groupId> <artifactId>shiro-redis</artifactId> <version>2.4.2.1-RELEASE</version></dependency> 改造ShiroConfig.java文件 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114/** * @author 作者 z77z * @date 创建时间:2017年2月10日 下午1:16:38 * */@Configurationpublic class ShiroConfig { @Autowired SysPermissionInitService sysPermissionInitService; @Value(\"${spring.redis.host}\") private String host; @Value(\"${spring.redis.port}\") private int port; @Bean public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 必须设置 SecurityManager shiroFilterFactoryBean.setSecurityManager(securityManager); // 如果不设置默认会自动寻找Web工程根目录下的\"/login.jsp\"页面 shiroFilterFactoryBean.setLoginUrl(\"/login\"); // 登录成功后要跳转的链接 shiroFilterFactoryBean.setSuccessUrl(\"/index\"); // 未授权界面; shiroFilterFactoryBean.setUnauthorizedUrl(\"/403\"); // 权限控制map. Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); // 从数据库获取 List<SysPermissionInit> list = sysPermissionInitService.selectAll(); for (SysPermissionInit sysPermissionInit : list) { filterChainDefinitionMap.put(sysPermissionInit.getUrl(), sysPermissionInit.getPermissionInit()); } shiroFilterFactoryBean .setFilterChainDefinitionMap(filterChainDefinitionMap); System.out.println(\"Shiro拦截器工厂类注入成功\"); return shiroFilterFactoryBean; } @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 设置realm. securityManager.setRealm(myShiroRealm()); // 自定义缓存实现 使用redis securityManager.setCacheManager(cacheManager()); // 自定义session管理 使用redis securityManager.setSessionManager(SessionManager()); return securityManager; } /** * 身份认证realm; (这个需要自己写,账号密码校验;权限等) * * @return */ @Bean public MyShiroRealm myShiroRealm() { MyShiroRealm myShiroRealm = new MyShiroRealm(); return myShiroRealm; } /** * 配置shiro redisManager * * @return */ public RedisManager redisManager() { RedisManager redisManager = new RedisManager(); redisManager.setHost(host); redisManager.setPort(port); redisManager.setExpire(1800);// 配置过期时间 // redisManager.setTimeout(timeout); // redisManager.setPassword(password); return redisManager; } /** * cacheManager 缓存 redis实现 * * @return */ public RedisCacheManager cacheManager() { RedisCacheManager redisCacheManager = new RedisCacheManager(); redisCacheManager.setRedisManager(redisManager()); return redisCacheManager; } /** * RedisSessionDAO shiro sessionDao层的实现 通过redis */ public RedisSessionDAO redisSessionDAO() { RedisSessionDAO redisSessionDAO = new RedisSessionDAO(); redisSessionDAO.setRedisManager(redisManager()); return redisSessionDAO; } /** * shiro session的管理 */ public DefaultWebSessionManager SessionManager() { DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); sessionManager.setSessionDAO(redisSessionDAO()); return sessionManager; }} 这里,因为使用的是redis来做容器缓存,所以要创建redisManager来配置shiro,SessionManager(),cacheManager()这两个类都是插件给我们写好了的,里面就是对shiro提供的接口的redis实现方式。 使用插件就是这么简单,直接启动程序,多访问几次具有权限的页面,查看控制台发现,权限认证方法:MyShiroRealm.doGetAuthorizationInfo()会只执行了一次。说明我们的缓存生效了。 总结到此,我们集成shiro和redis,学习了一下功能的实现: 用户必须要登陆之后才能访问定义链接,否则跳转到登录页面,被禁用户不能登录。并且对一些敏感操作链接设置权限,只有满足权限的才可以访问。 每个链接的权限信息保存在数据库,可以动态进行设置,并且热加载权限。 使用redis对shiro的用户信息进行缓存,不用每次都去执行MyShiroRealm.doGetAuthorizationInfo()权限认证方法。 之前有很多同学下载我的项目时,运行会报错,那是因为最近都在不断修改提交,有可能会出现版本问题,现在我在我的码云上面创建了stable_version分支,都是可以跑起来的。sqltable放在resource目录下面。 下一博,我应该会写对在线用户的管理,踢出登录的功能学习记录。 香蕉硬币点赞走一波啦。。。。。。","categories":[],"tags":[{"name":"springboot","slug":"springboot","permalink":"http://z77z.oschina.io/tags/springboot/"},{"name":"redis","slug":"redis","permalink":"http://z77z.oschina.io/tags/redis/"},{"name":"shiro","slug":"shiro","permalink":"http://z77z.oschina.io/tags/shiro/"}]},{"title":"SpringBoot+shiro整合学习之登录认证和权限控制","slug":"SpringBoot+shiro整合学习之登录认证和权限控制","date":"2017-02-13T14:46:58.000Z","updated":"2017-02-13T14:46:28.172Z","comments":true,"path":"2017/02/13/SpringBoot+shiro整合学习之登录认证和权限控制/","link":"","permalink":"http://z77z.oschina.io/2017/02/13/SpringBoot+shiro整合学习之登录认证和权限控制/","excerpt":"","text":"学习任务目标 用户必须要登陆之后才能访问定义链接,否则跳转到登录页面。 对链接进行权限控制,只有当当前登录用户有这个链接访问权限才可以访问,否则跳转到指定页面。 输入错误密码用户名或则用户被设置为静止登录,返回相应json串信息。 我是用的是之前搭建的一个springboot+mybatisplus+jsp的一个基础框架。在这之上进行shiro的整合。需要的同学可以去我的码云下载。 个人博客:http://z77z.oschina.io/ 此项目下载地址:https://git.oschina.net/z77z/springboot_mybatisplus 导入shiro依赖包到pom.xml123456<!-- shiro权限控制框架 --><dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.3.2</version></dependency> 采用RBAC模式建立数据库 RBAC 是基于角色的访问控制(Role-Based Access Control )在 RBAC 中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便。 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253/*表结构插入*/DROP TABLE IF EXISTS `u_permission`;CREATE TABLE `u_permission` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `url` varchar(256) DEFAULT NULL COMMENT 'url地址', `name` varchar(64) DEFAULT NULL COMMENT 'url描述', PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8;/*Table structure for table `u_role` */DROP TABLE IF EXISTS `u_role`;CREATE TABLE `u_role` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(32) DEFAULT NULL COMMENT '角色名称', `type` varchar(10) DEFAULT NULL COMMENT '角色类型', PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;/*Table structure for table `u_role_permission` */DROP TABLE IF EXISTS `u_role_permission`;CREATE TABLE `u_role_permission` ( `rid` bigint(20) DEFAULT NULL COMMENT '角色ID', `pid` bigint(20) DEFAULT NULL COMMENT '权限ID') ENGINE=InnoDB DEFAULT CHARSET=utf8;/*Table structure for table `u_user` */DROP TABLE IF EXISTS `u_user`;CREATE TABLE `u_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `nickname` varchar(20) DEFAULT NULL COMMENT '用户昵称', `email` varchar(128) DEFAULT NULL COMMENT '邮箱|登录帐号', `pswd` varchar(32) DEFAULT NULL COMMENT '密码', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间', `status` bigint(1) DEFAULT '1' COMMENT '1:有效,0:禁止登录', PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8;/*Table structure for table `u_user_role` */DROP TABLE IF EXISTS `u_user_role`;CREATE TABLE `u_user_role` ( `uid` bigint(20) DEFAULT NULL COMMENT '用户ID', `rid` bigint(20) DEFAULT NULL COMMENT '角色ID') ENGINE=InnoDB DEFAULT CHARSET=utf8; Dao层代码的编写 Dao层的entity,service,mapper等我是采用mybatisplus的代码自动生成工具生成的,具备了单表的增删改查功能和分页功能,比较方便,这里我就不贴代码了。 配置shiroShiroConfig.java 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869/** * @author 作者 z77z * @date 创建时间:2017年2月10日 下午1:16:38 * */@Configurationpublic class ShiroConfig { /** * ShiroFilterFactoryBean 处理拦截资源文件问题。 * 注意:单独一个ShiroFilterFactoryBean配置是或报错的,以为在 * 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager * * Filter Chain定义说明 1、一个URL可以配置多个Filter,使用逗号分隔 2、当设置多个过滤器时,全部验证通过,才视为通过 * 3、部分过滤器可指定参数,如perms,roles * */ @Bean public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 必须设置 SecurityManager shiroFilterFactoryBean.setSecurityManager(securityManager); // 如果不设置默认会自动寻找Web工程根目录下的\"/login.jsp\"页面 shiroFilterFactoryBean.setLoginUrl(\"/login\"); // 登录成功后要跳转的链接 shiroFilterFactoryBean.setSuccessUrl(\"/index\"); // 未授权界面; shiroFilterFactoryBean.setUnauthorizedUrl(\"/403\"); // 拦截器. Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); // 配置不会被拦截的链接 顺序判断 filterChainDefinitionMap.put(\"/static/**\", \"anon\"); filterChainDefinitionMap.put(\"/ajaxLogin\", \"anon\"); // 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了 filterChainDefinitionMap.put(\"/logout\", \"logout\"); filterChainDefinitionMap.put(\"/add\", \"perms[权限添加]\"); // <!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了; // <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问--> filterChainDefinitionMap.put(\"/**\", \"authc\"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); System.out.println(\"Shiro拦截器工厂类注入成功\"); return shiroFilterFactoryBean; } @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 设置realm. securityManager.setRealm(myShiroRealm()); return securityManager; } /** * 身份认证realm; (这个需要自己写,账号密码校验;权限等) * * @return */ @Bean public MyShiroRealm myShiroRealm() { MyShiroRealm myShiroRealm = new MyShiroRealm(); return myShiroRealm; }} 登录认证实现 在认证、授权内部实现机制中都有提到,最终处理都将交给Real进行处理。因为在Shiro中,最终是通过Realm来获取应用程序中的用户、角色及权限信息的。通常情况下,在Realm中会直接从我们的数据源中获取Shiro需要的验证信息。可以说,Realm是专用于安全框架的DAO. Shiro的认证过程最终会交由Realm执行,这时会调用Realm的getAuthenticationInfo(token)方法。该方法主要执行以下操作: 1、检查提交的进行认证的令牌信息 2、根据令牌信息从数据源(通常为数据库)中获取用户信息 3、对用户信息进行匹配验证。 4、验证通过将返回一个封装了用户信息的AuthenticationInfo实例。 5、验证失败则抛出AuthenticationException异常信息。 而在我们的应用程序中要做的就是自定义一个Realm类,继承AuthorizingRealm抽象类,重载doGetAuthenticationInfo(),重写获取用户信息的方法。 doGetAuthenticationInfo的重写 123456789101112131415161718192021222324252627282930313233343536/*** 认证信息.(身份验证) : Authentication 是用来验证用户身份 * * @param token * @return * @throws AuthenticationException */@Overrideprotected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken authcToken) throws AuthenticationException { System.out.println(\"身份认证方法:MyShiroRealm.doGetAuthenticationInfo()\"); ShiroToken token = (ShiroToken) authcToken; Map<String, Object> map = new HashMap<String, Object>(); map.put(\"nickname\", token.getUsername()); map.put(\"pswd\", token.getPswd()); SysUser user = null; // 从数据库获取对应用户名密码的用户 List<SysUser> userList = sysUserService.selectByMap(map); if(userList.size()!=0){ user = userList.get(0); } if (null == user) { throw new AccountException(\"帐号或密码不正确!\"); }else if(user.getStatus()==0){ /** * 如果用户的status为禁用。那么就抛出<code>DisabledAccountException</code> */ throw new DisabledAccountException(\"帐号已经禁止登录!\"); }else{ //更新登录时间 last login time user.setLastLoginTime(new Date()); sysUserService.updateById(user); } return new SimpleAuthenticationInfo(user, user.getPswd(), getName());} 通俗的说,这个的重写就是我们第一个学习目标的实现。 链接权限的实现 shiro的权限授权是通过继承AuthorizingRealm抽象类,重载doGetAuthorizationInfo(); 当访问到页面的时候,链接配置了相应的权限或者shiro标签才会执行此方法否则不会执行,所以如果只是简单的身份认证没有权限的控制的话,那么这个方法可以不进行实现,直接返回null即可。 在这个方法中主要是使用类:SimpleAuthorizationInfo 进行角色的添加和权限的添加。 authorizationInfo.addRole(role.getRole()); authorizationInfo.addStringPermission(p.getPermission()); 当然也可以添加set集合:roles是从数据库查询的当前用户的角色,stringPermissions是从数据库查询的当前用户对应的权限 authorizationInfo.setRoles(roles); authorizationInfo.setStringPermissions(stringPermissions); 就是说如果在shiro配置文件中添加了filterChainDefinitionMap.put(“/add”, “perms[权限添加]”);就说明访问/add这个链接必须要有“权限添加”这个权限才可以访问, 如果在shiro配置文件中添加了filterChainDefinitionMap.put(“/add”, “roles[100002],perms[权限添加]”);就说明访问/add这个链接必须要有“权限添加”这个权限和具有“100002”这个角色才可以访问。 123456789101112131415161718192021222324252627282930313233/*** 授权 */@Overrideprotected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals) { System.out.println(\"权限认证方法:MyShiroRealm.doGetAuthenticationInfo()\"); SysUser token = (SysUser)SecurityUtils.getSubject().getPrincipal(); String userId = token.getId(); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //根据用户ID查询角色(role),放入到Authorization里。 /*Map<String, Object> map = new HashMap<String, Object>(); map.put(\"user_id\", userId); List<SysRole> roleList = sysRoleService.selectByMap(map); Set<String> roleSet = new HashSet<String>(); for(SysRole role : roleList){ roleSet.add(role.getType()); }*/ //实际开发,当前登录用户的角色和权限信息是从数据库来获取的,我这里写死是为了方便测试 Set<String> roleSet = new HashSet<String>(); roleSet.add(\"100002\"); info.setRoles(roleSet); //根据用户ID查询权限(permission),放入到Authorization里。 /*List<SysPermission> permissionList = sysPermissionService.selectByMap(map); Set<String> permissionSet = new HashSet<String>(); for(SysPermission Permission : permissionList){ permissionSet.add(Permission.getName()); }*/ Set<String> permissionSet = new HashSet<String>(); permissionSet.add(\"权限添加\"); info.setStringPermissions(permissionSet); return info;} 这个类的实现是完成了我们学习目标的第二个任务。 编写web层的代码登录页面: controller 1234567891011121314151617181920212223242526272829//跳转到登录表单页面@RequestMapping(value=\"login\")public String login() { return \"login\";}/** * ajax登录请求 * @param username * @param password * @return */@RequestMapping(value=\"ajaxLogin\",method=RequestMethod.POST)@ResponseBodypublic Map<String,Object> submitLogin(String username, String password,Model model) { Map<String, Object> resultMap = new LinkedHashMap<String, Object>(); try { ShiroToken token = new ShiroToken(username, password); SecurityUtils.getSubject().login(token); resultMap.put(\"status\", 200); resultMap.put(\"message\", \"登录成功\"); } catch (Exception e) { resultMap.put(\"status\", 500); resultMap.put(\"message\", e.getMessage()); } return resultMap;} jsp 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748<%@ page language=\"java\" contentType=\"text/html; charset=utf-8\" pageEncoding=\"utf-8\"%><% String path = request.getContextPath(); String basePath = request.getScheme() + \"://\" + request.getServerName() + \":\" + request.getServerPort() + path;%><!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\"><html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"><script type=\"text/javascript\" src=\"<%=basePath%>/static/js/jquery-1.11.3.js\"></script><title>登录</title></head><body> 错误信息: <h4 id=\"erro\"></h4> <form> <p> 账号:<input type=\"text\" name=\"username\" id=\"username\" value=\"admin\" /> </p> <p> 密码:<input type=\"text\" name=\"password\" id=\"password\" value=\"123\" /> </p> <p> <input type=\"button\" id=\"ajaxLogin\" value=\"登录\" /> </p> </form></body><script> var username = $(\"#username\").val(); var password = $(\"#password\").val(); $(\"#ajaxLogin\").click(function() { $.post(\"/ajaxLogin\", { \"username\" : username, \"password\" : password }, function(result) { if (result.status == 200) { location.href = \"/index\"; } else { $(\"#erro\").html(result.message); } }); });</script></html> 主页页面controller 12345678910111213141516171819202122//跳转到主页@RequestMapping(value=\"index\")public String index() { return \"index\";}/*** 退出 * @return */@RequestMapping(value=\"logout\",method =RequestMethod.GET)@ResponseBodypublic Map<String,Object> logout(){ Map<String, Object> resultMap = new LinkedHashMap<String, Object>(); try { //退出 SecurityUtils.getSubject().logout(); } catch (Exception e) { System.err.println(e.getMessage()); } return resultMap;} jsp 1234567891011121314151617181920212223242526<%@ page language=\"java\" contentType=\"text/html; charset=UTF-8\" pageEncoding=\"UTF-8\"%><% String path = request.getContextPath(); String basePath = request.getScheme() + \"://\" + request.getServerName() + \":\" + request.getServerPort() + path;%><!DOCTYPE html><html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"><script type=\"text/javascript\" src=\"<%=basePath%>/static/js/jquery-1.11.3.js\"></script><title>Insert title here</title></head><body> helloJsp <input type=\"button\" id=\"logout\" value=\"退出登录\" /></body><script type=\"text/javascript\"> $(\"#logout\").click(function(){ location.href=\"/logout\"; });</script></html> 添加操作页面 controller 1234@RequestMapping(value=\"add\")public String add() { return \"add\";} jsp 1234567891011121314151617181920<%@ page language=\"java\" contentType=\"text/html; charset=UTF-8\" pageEncoding=\"UTF-8\"%><% String path = request.getContextPath(); String basePath = request.getScheme() + \"://\" + request.getServerName() + \":\" + request.getServerPort() + path;%><!DOCTYPE html><html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"><script type=\"text/javascript\" src=\"<%=basePath%>/static/js/jquery-1.11.3.js\"></script><title>Insert title here</title></head><body>具有添加权限</body></html> 测试任务一 编写好后就可以启动程序,访问index页面,由于没有登录就会跳转到login页面。 登录之后就会跳转到index页面,点击退出登录后,有直接在浏览器中输入index页面访问,又会跳转到login页面 上面这些操作时候触发MyShiroRealm.doGetAuthenticationInfo()这个方法,也就是登录认证的方法。 任务二 登录之后访问add页面成功访问,在shiro配置文件中改变add的访问权限为 filterChainDefinitionMap.put(“/add”,”perms[权限删除]”); 再重新启动程序,登录后访问,会重定向到/403页面,由于没有编写403页面,报404错误。 上面这些操作,会触发权限认证方法:MyShiroRealm.doGetAuthorizationInfo(),每访问一次就会触发一次。 任务三 输入错误的用户名或则密码,返回“帐号或密码不正确!”的错误信息,在数据库中把一个用户的状态改为被禁用,再登陆,提示“帐号已经禁止登录!”的错误信息 上面的操作,是在MyShiroRealm.doGetAuthenticationInfo()登录认证的方法中实现的,通过查询数据库判断当前登录用户是否被禁用,具体可以去看源码。 总结当然shiro很强大,这仅仅是完成了登录认证和权限管理这两个功能,接下来我会继续学习和分享,说说接下来的学习路线吧: shiro+redis集成,避免每次访问有权限的链接都会去执行MyShiroRealm.doGetAuthenticationInfo()方法来查询当前用户的权限,因为实际情况中权限是不会经常变得,这样就可以使用redis进行权限的缓存。 实现shiro链接权限的动态加载,之前要添加一个链接的权限,要在shiro的配置文件中添加filterChainDefinitionMap.put(“/add”, “roles[100002],perms[权限添加]”),这样很不方便管理,一种方法是将链接的权限使用数据库进行加载,另一种是通过init配置文件的方式读取。 Shiro 自定义权限校验Filter定义,及功能实现。 Shiro Ajax请求权限不满足,拦截后解决方案。这里有一个前提,我们知道Ajax不能做页面redirect和forward跳转,所以Ajax请求假如没登录,那么这个请求给用户的感觉就是没有任何反应,而用户又不知道用户已经退出了。 Shiro JSP标签使用。 Shiro 登录后跳转到最后一个访问的页面 在线显示,在线用户管理(踢出登录)。 登录注册密码加密传输。 集成验证码。 记住我的功能。关闭浏览器后还是登录状态。 还有没有想到的后面再说,欢迎大家提出一些建议。","categories":[],"tags":[{"name":"springboot","slug":"springboot","permalink":"http://z77z.oschina.io/tags/springboot/"},{"name":"shiro","slug":"shiro","permalink":"http://z77z.oschina.io/tags/shiro/"}]},{"title":"SB集成Redis学习笔记之实际应用场景-java干货","slug":"SB集成Redis学习笔记之实际应用场景-java干货","date":"2017-02-06T13:22:58.000Z","updated":"2017-02-06T13:29:03.430Z","comments":true,"path":"2017/02/06/SB集成Redis学习笔记之实际应用场景-java干货/","link":"","permalink":"http://z77z.oschina.io/2017/02/06/SB集成Redis学习笔记之实际应用场景-java干货/","excerpt":"","text":"之前利用springBoot中的缓存机制,使用Redis作为缓存容器,做了一个缓存的简单Demo,当然Redis不仅仅可以用来做缓存的容器,还有很多开发的实际场景中会用到Redis的特性,通过几天的学习,现将学习的成果整理分享给大家。希望大家也多多讨论,提供跟多的使用场景,来熟练掌握在springboot中的使用。 个人博客:http://z77z.oschina.io/ 此项目下载地址:https://git.oschina.net/z77z/springboot_mybatisplus Redis的特性 Strings:Strings 数据结构是简单的key-value类型,value其实不仅是String,也可以是数字.常用命令: set,get,decr,incr,mget 等。常用方法: 获取字符串长度 往字符串append内容 设置和获取字符串的某一段内容 设置及获取字符串的某一位(bit) 批量设置一系列字符串的内容 Hashs:Redis Hash对应Value内部实际就是一个HashMap,常用命令:hget,hset,hgetall 等。 Lists:Redis list的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构。常用命令:lpush,rpush,lpop,rpop,lrange等。 Sets:Sets 集合的概念就是一堆不重复值的组合。Redis还为集合提供了求交集、并集、差集等操作,可以非常方便的实现如共同关注、共同喜好、二度好友等功能。常用命令:sadd,spop,smembers,sunion 等。 Sorted Sets:Redis sorted set的使用场景与set类似,区别是set不是自动有序的。sorted set可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。常用命令:zadd,zrange,zrem,zcard等 下面就根据这几个特性,也就是Redis支持的数据类型,来完成以下场景的实现。 场景一:简单计数功能 Redis是一个很好的计数器,计数器是 Redis 的原子性自增操作可实现的最直观的模式了,它的想法相当简单:每当某个操作发生时,向 Redis 发送一个 INCR 命令。使用场景比如网站的访问数,注册用户数,文章的点赞数,高并发的秒杀活动,分布式序列号生成等等统计计数的功能实现。Redis 解决这类计数问题得心应手,相比关系数据库速度更快,消耗资源更少。还可以通过set()方法来重置计数。 1234567891011121314151617// 简单计数 @Test public void test1() { try { ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue(); //计数前打印 System.out.println(opsForValue.get(\"test1\")); for (int i = 0; i < 100; i++) { //计数,第一个参数为key值,第二个参数为每次增加计数的单位 opsForValue.increment(\"test1\", 1); } //计数后打印 System.out.println(opsForValue.get(\"test1\")); } catch (Exception e) { e.printStackTrace(); } } 场景二:按时间统计计数 有时候除了简单的计数外,比如注册用户数需要按日统计,处理方法比较简单,把日期带入计数器 key 就可以。以此类推,还可以按其他方式进行统计计数,只需要把统计的方式添加到key值就可以了 12345678910111213141516// 按时间计数 @Test public void test2() { //将日期添加到key值中 String key = \"test2_\" + new SimpleDateFormat(\"yyyy-MM-dd\").format(new Date()); try { ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue(); System.out.println(opsForValue.get(key)); for (int i = 0; i < 100; i++) { opsForValue.increment(key, 1); } System.out.println(opsForValue.get(key)); } catch (Exception e) { e.printStackTrace(); } } 场景三:按模糊Key值查询 在按条件统计计数的时候,把时间加入到了key值中,有时候要只是查询某个对象的统计数时,就可以使用模糊Key值查询。 123456789101112131415// 模糊K值查询 @Test public void test3() { try { ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue(); //先获取前缀为test的Key值列表。 Set<String> keys = stringRedisTemplate.keys(\"test*\"); //遍历满足条件的Key值获取对应的value值 for (String a : keys) { System.out.println(a + \":\" + opsForValue.get(a)); } } catch (Exception e) { e.printStackTrace(); } } 场景四:设置Key的有效时间(防止高并发访问) 在redis中可以设置key值的有效时间,用户访问链接的时候,将用户的唯一信息比如ip地址等为key值,时间为value值,在redis中记录一下,在用户再次访问的时候,通过key值获取前一次访问的时间,比较时间的间隔,如果低于阀值,就拒绝这次请求,防止用户多次访问。这里只是写下在spring的RedisTemplate接口怎么使用。具体的逻辑实现自己搞定。 123456789101112131415161718192021222324// 设置key值的有效时间 @Test public void test4() { try { ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue(); opsForValue.set(\"test4\", \"test4\"); System.out.println(opsForValue.get(\"test4\")); // TimeUnit.SECONDS:解释定时参数的单位 // MICROSECONDS 微秒 一百万分之一秒(就是毫秒/1000) // MILLISECONDS 毫秒 千分之一秒 // NANOSECONDS 毫微秒 十亿分之一秒(就是微秒/1000) // SECONDS 秒 // MINUTES 分钟 // HOURS 小时 // DAYS 天 if(stringRedisTemplate.expire(\"test4\", 5, TimeUnit.SECONDS)){ System.out.println(\"设置过期时间成功,等待。。。。\"); Thread.sleep(5001); } System.out.println(opsForValue.get(\"test4\")); } catch (Exception e) { e.printStackTrace(); } } 场景五:使用hashs存储获取修改java对象 在实际开发中,我们经常将一些结构化的信息打包成HashMap,在客户端序列化后存储为一个字符串的值,比如用户的昵称、年龄、性别、积分等,这时候在需要修改其中某一项时,通常需要将所有值取出反序列化后,修改某一项的值,再序列化存储回去。这样不仅增大了开销,也不适用于一些可能并发操作的场合(比如两个并发的操作都需要修改积分)。 而Redis的Hash结构可以使你像在数据库中Update一个属性一样只修改某一项属性值。因为Redis的Hash结构是以对象的名字作为redis的key值,以对象的唯一属性值作为hash的key值,以对象来作为redis的value值。结构图如下: hashs存储结构图 12345678// 使用hashs存储获取对象 @Test public void test5(){ BeautifulPictures beautifulPictures = beautifulPicturesService.selectById(1); HashOperations<String, Object, BeautifulPictures> hash = redisTemplate.opsForHash(); hash.put(\"test5\",beautifulPictures.getId(),beautifulPictures); System.out.println(hash.get(\"test5\", beautifulPictures.getId())); } 场景六:使用lists有序存储读取 适用于获取最近N个操作的数据。 123456789101112131415//使用lists存储读取 有序 @Test public void test6(){ ListOperations<String, String> list = stringRedisTemplate.opsForList(); list.leftPush(\"test6\", \"1\"); list.leftPush(\"test6\", \"2\"); list.leftPush(\"test6\", \"3\"); list.leftPush(\"test6\", \"4\"); list.leftPush(\"test6\", \"5\"); list.leftPush(\"test6\", \"6\"); list.leftPush(\"test6\", \"7\"); //保持链表只有3位 list.trim(\"test6\", 0, 2); System.out.println(list.range(\"test6\", 0, list.size(\"test6\")-1)); } 场景七:使用sets存储读取 无序 去重 求差集,交集,并集1234567891011//使用set存储读取 无序 去重 求差集,交集,并集 @Test public void test7(){ SetOperations<String, String> set = stringRedisTemplate.opsForSet(); set.add(\"test7_1\", \"2\", \"1\",\"2\",\"3\",\"4\",\"4\",\"3\"); set.add(\"test7_2\", \"2\", \"6\",\"2\",\"3\",\"7\",\"6\",\"5\"); System.out.println(\"全部成员\"+set.members(\"test7_1\")); System.out.println(\"差集\"+set.difference(\"test7_1\", \"test7_2\")); System.out.println(\"交集\"+set.intersect(\"test7_1\", \"test7_2\")); System.out.println(\"并集\"+set.union(\"test7_1\", \"test7_2\")); } 场景八:Sorted Set 存取数据 排序123456789101112//Sorted Set 存取数据 排序 相比sets 保存时多一个权重参数score,相当于按照此参数来排序 @Test public void test8(){ ZSetOperations<String, String> zSet = stringRedisTemplate.opsForZSet(); zSet.add("test8", "use1", 9); zSet.add("test8", "use2", 1); zSet.add("test8", "use3", 5); zSet.add("test8", "use4", 9); //对应的score值增加 //zSet.incrementScore("test8", "use1", 1); System.out.println(zSet.reverseRange("test8", 0, zSet.size("test8")-1)); }","categories":[],"tags":[{"name":"springboot","slug":"springboot","permalink":"http://z77z.oschina.io/tags/springboot/"},{"name":"redis","slug":"redis","permalink":"http://z77z.oschina.io/tags/redis/"}]},{"title":"SpringBoot缓存注解学习笔记之Redis","slug":"SpringBoot缓存注解学习笔记之Redis","date":"2017-01-30T08:41:58.000Z","updated":"2017-01-30T08:41:26.298Z","comments":true,"path":"2017/01/30/SpringBoot缓存注解学习笔记之Redis/","link":"","permalink":"http://z77z.oschina.io/2017/01/30/SpringBoot缓存注解学习笔记之Redis/","excerpt":"","text":"之前用SpringBoot+MyBatisPlus+SpringMVC整合搭建了一个基础web开发框架,使用这三个框架搭建出来项目结构非常的清爽,没有过多的配置文件,各个模块之间有清晰的联系,非常适合敏捷开发。 最近学习了Redis这个基于内存的,Key-Value数据形式的高性能数据库,感觉学习了入门之后很简单,没有体会到它具体能干嘛,我就想着使用Redis这个数据库来整合之前搭建的框架,利用Spring中的缓存机制,将查询的信息缓存到Redis中。 安装Redis Window 下安装 下载地址:https://github.com/MSOpenTech/redis/releases。 Redis 支持32 位和64 位。这个需要根据你系统平台的实际情况选择,这里我们下载 Redis-x64-xxx.zip压缩包到 C盘的tools目录中,解压后,将文件夹重新命名为 redis。 下载Redis版本 打开一个 cmd 窗口 使用cd命令切换目录到 C:\\tools\\redis 运行 redis-server.exe redis.windows.conf 。如果想方便的话,可以把 redis 的路径加到系统的环境变量里,这样就省得再输路径了,后面的那个 redis.windows.conf 可以省略,如果省略,会启用默认的。输入之后,会显示如下界面: 启动成功后 启动成功后不要关闭命令窗口,不然redis服务也会关闭。如果不想每次使用都一直开着这个命令窗口,可以将redis服务添加到windows的服务中: 12345安装命令:redis-server.exe --service-install redis.windows.conf --loglevel verbose 卸载命令:redis-server --service-uninstall 安装成功后,可以在windows的服务管理中对redis进行管理,就不用每次都打开命令窗口来启动redis服务了,如下图: 在windows中管理redis服务 获取之前项目 环境我就直接在之前的整合框架上进行搭建,之前项目下载地址:https://git.oschina.net/z77z/springboot_mybatisplus注意:之前搭建这个框架的时候我为了获取基础数据,在启动springboot的时候也启动了爬虫程序,如果不想每次启动都启动爬虫可以注释掉启动类中的run方法。 添加Redis依赖到pom.xml中1234<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-redis</artifactId></dependency> 在application.properties中添加配置12345678910111213141516171819# REDIS (RedisProperties)# Redis数据库索引(默认为0)spring.redis.database=0# Redis服务器地址spring.redis.host=localhost# Redis服务器连接端口spring.redis.port=6379# Redis服务器连接密码(默认为空)spring.redis.password=# 连接池最大连接数(使用负值表示没有限制)spring.redis.pool.max-active=8# 连接池最大阻塞等待时间(使用负值表示没有限制)spring.redis.pool.max-wait=-1# 连接池中的最大空闲连接spring.redis.pool.max-idle=8# 连接池中的最小空闲连接spring.redis.pool.min-idle=0# 连接超时时间(毫秒)spring.redis.timeout=0 我们需要做的配置到这里就已经完成了,Spring Boot会在侦测到存在Redis的依赖并且Redis的配置是可用的情况下,使用RedisCacheManager初始化CacheManager。也就是说要使用缓存的话,SpringBoot就会选择Redis来作为缓存的容器。 编写一个简单的redis读写测试列123456789101112131415161718192021/** * redis读写测试 * @author z77z * */@RunWith(SpringJUnit4ClassRunner.class)@SpringBootTest(classes = Application.class)public class RedisCacheTest { @Autowired StringRedisTemplate stringRedisTemplate; @Test public void redisTest() throws Exception { //保存字符串 stringRedisTemplate.opsForValue().set(\"aaa\", \"111\"); //读取字符串 String aaa = stringRedisTemplate.opsForValue().get(\"aaa\"); System.out.println(aaa); }} 打印结果: redis读写测试结果 编写缓存测试列不使用缓存: 123456789101112131415/** * 获取数据,并且做缓存处理 * @author z77z * */@Componentpublic class RedisCache { @Autowired BeautifulPicturesService beautifulPicturesService; //@Cacheable(value = \"BeautifulPictures\") public BeautifulPictures getBeautifulPicturesList(String id) { return beautifulPicturesService.selectById(id); }} 123456789101112131415161718192021222324252627282930313233343536373839/** * 测试类 * @author z77z * */@RunWith(SpringJUnit4ClassRunner.class)@SpringBootTest(classes = Application.class)public class RedisCacheTest { @Autowired BeautifulPicturesService beautifulPicturesService; @Autowired StringRedisTemplate stringRedisTemplate; @Autowired RedisCache redisCache; @Test public void redisTest() throws Exception { //保存字符串 stringRedisTemplate.opsForValue().set(\"aaa\", \"111\"); //读取字符串 String aaa = stringRedisTemplate.opsForValue().get(\"aaa\"); System.out.println(aaa); } @Test public void CacheTest() { BeautifulPictures beautifulPicture = redisCache.getBeautifulPicturesList(\"1011\"); System.out.println(\"第一次查询结果:\"); System.out.println(beautifulPicture); BeautifulPictures beautifulPicture1 = redisCache.getBeautifulPicturesList(\"1011\"); System.out.println(\"第二次查询结果:\"); System.out.println(beautifulPicture1); }} 执行结果: 可以从日志中看出两次查询都是执行了sql,也就是执行了getBeautifulPicturesList这个方法 使用缓存: 在getBeautifulPicturesList方法的上面添加@Cacheable(value = “BeautifulPictures”)注解,当请求这个方法时会先判断缓存中是否存在,存在就在缓存中获取,不会执行这个方法。不存在就正常执行这个方法获取返回值并且存如缓存中。添加注解后执行结果如下: 添加缓存注释后 从日志中可以看出,第一次查询的时候执行的sql,而第二次查询的时候没有执行sql,说明是从缓存中获取的数据。 缓存数据一致性保证 CRUD (Create 创建,Retrieve 读取,Update 更新,Delete 删除) 操作中,除了 R具备幂等性,其他三个发生的时候都可能会造成缓存结果和数据库不一致。为了保证缓存数据的一致性,在进行 CUD 操作的时候我们需要对可能影响到的缓存进行更新或者清除。如下: 1234567891011121314151617181920212223242526/** * 获取数据,并且做缓存处理 * @author z77z * */@Componentpublic class RedisCache { @Autowired BeautifulPicturesService beautifulPicturesService; //查询 @Cacheable(value = \"beautifulPictures\") public BeautifulPictures getBeautifulPicturesList(String id) { return beautifulPicturesService.selectById(id); } //修改 @CachePut(value = \"beautifulPictures\") public void updateBeautifulPicture(String id) { BeautifulPictures beautifulPictures = new BeautifulPictures(); beautifulPictures.setTitle(\"Title被我修改了一下,哈哈\"); beautifulPictures.setId(id); beautifulPicturesService.updateById(beautifulPictures); }} 123456789101112131415161718192021222324252627282930313233343536373839404142434445/** * 测试类 * @author z77z * */@RunWith(SpringJUnit4ClassRunner.class)@SpringBootTest(classes = Application.class)public class RedisCacheTest { @Autowired BeautifulPicturesService beautifulPicturesService; @Autowired StringRedisTemplate stringRedisTemplate; @Autowired RedisCache redisCache; @Test public void redisTest() throws Exception { //保存字符串 stringRedisTemplate.opsForValue().set(\"aaa\", \"111\"); //读取字符串 String aaa = stringRedisTemplate.opsForValue().get(\"aaa\"); System.out.println(aaa); } @Test public void CacheTest() { String id = \"1\"; BeautifulPictures beautifulPicture = redisCache.getBeautifulPicturesList(id); System.out.println(\"第一次查询结果:\"); System.out.println(beautifulPicture); BeautifulPictures beautifulPicture1 = redisCache.getBeautifulPicturesList(id); System.out.println(\"第二次查询结果:\"); System.out.println(beautifulPicture1); redisCache.updateBeautifulPicture(id); BeautifulPictures beautifulPicture2 = redisCache.getBeautifulPicturesList(id); System.out.println(\"第三次查询结果:\"); System.out.println(beautifulPicture2); }} 保持缓存一致性测试结果: 保持缓存的一致性测试 在会导致数据发生改变的方法上添加@CachePut(value = "beautifulPictures")注解,添加后会更新缓存中的值,并且每次都会正常执行方法内容。 SpringBoot缓存注解详解 @Cacheable:作用是主要针对方法配置,能够根据方法的请求参数对其结果进行缓存 主要参数说明: value:缓存的名称,在 spring 配置文件中定义,必须指定至少一个,例如:@Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”}。 key:缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合,例如:@Cacheable(value=”testcache”,key=”#userName”)。 condition:缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存,例如:@Cacheable(value=”testcache”,condition=”#userName.length()>2。 @CachePut:作用是主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发真实方法的调用 主要参数说明: value,key和condition参数配置和@Cacheable一样。 @CacheEvict:作用是主要针对方法配置,能够根据一定的条件对缓存进行清空 主要参数说明: value,key和condition参数配置和@Cacheable一样。 allEntries:是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存,例如:@CachEvict(value=”testcache”,allEntries=true)。 beforeInvocation:是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存,例如@CachEvict(value=”testcache”,beforeInvocation=true)。 另外说下: @cache(“something”);这个相当于save()操作,@cachePut相当于Update()操作,只要他标示的方法被调用,那么都会缓存起来,而@cache则是先看下有没已经缓存了,然后再选择是否执行方法。@CacheEvict相当于Delete()操作。用来清除缓存用的。 荆轲~刺秦王~~","categories":[],"tags":[{"name":"springboot","slug":"springboot","permalink":"http://z77z.oschina.io/tags/springboot/"},{"name":"redis","slug":"redis","permalink":"http://z77z.oschina.io/tags/redis/"},{"name":"缓存","slug":"缓存","permalink":"http://z77z.oschina.io/tags/缓存/"}]},{"title":"SpringBoot+SpringMVC+MybatisPlus框架整合练习之【美女图片】爬虫---图文详细流程","slug":"SpringBoot+SpringMVC+MybatisPlus框架整合练习之【美女图片】爬虫---图文详细流程","date":"2017-01-22T16:25:58.000Z","updated":"2017-01-30T09:20:54.550Z","comments":true,"path":"2017/01/23/SpringBoot+SpringMVC+MybatisPlus框架整合练习之【美女图片】爬虫---图文详细流程/","link":"","permalink":"http://z77z.oschina.io/2017/01/23/SpringBoot+SpringMVC+MybatisPlus框架整合练习之【美女图片】爬虫---图文详细流程/","excerpt":"","text":"最近浏览很多博客,学习了不少新的知识,收获颇多,就想着能不能将新学的知识整合一下来练练手,提高自己撸代码搭框架的能力,还有就是给大家一个新年福利,爬一爬美女图片网站。上车请刷卡,哈哈。顺便就拿这爬来的数据作为基础数据来整合最近学习的框架。一劳多得。当然,本文还是以框架整合为主,爬取美女图片只是为了获取基础数据而已啦!! 现在我将我的学习成果分享出来,还望各位大神多多指点,有些不规范的地方还望大家指出,多多讨论学习,共同进步。源码我已经托管到我的码云上面,大家可以进我的博客查看,如果想要图片资源的话在评论区留下邮箱吧。 、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、个人博客地址:http://z77z.oschina.io/,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 先上成果(19禁!!)爬取的图片,一共爬了一万多张,够大家玩一阵了。。。——–羞涩。。 爬取的图片 数据库: 存储每个图片集合的链接,标题等信息。 存储每张图片的链接,并且和图片集合表关联。 图片信息表 项目整体搭建出来后的效果 项目文件结构 使用工具,框架介绍 这里只介绍下最近学习的框架,其他在后面用到在做介绍。这两个框架都是对现有的spring和mybatis的一个提升,而并不是替代之前的框架,使开发者能够达到敏捷开发的目的。只推荐老司机学习,新手还是建议从基础入手。 SpringBoot Spring由于其繁琐的配置,一度被人认为“配置地狱”,各种XML、Annotation配置,让人眼花缭乱,而且如果出错了也很难找出原因。想想之前搭建一个SSM框架,配置文件相当恼火,springboot的出现就大大减少了这些配置,甚至可以零配置文件。这里推荐一个此框架学习的博客链接:http://blog.720ui.com/page/3/ MybatisPlus: 这个框架是国内的大神编写的,我个人认为这就是一个mybatis的一个增强工具包,好处请大家自行去官方文档查阅,这里就不再赘述了。文档链接:http://mp.baomidou.com/docs/index.html 建立数据库 这里数据来源是爬虫爬取的。这里爬虫本身就不多介绍,我之前已经写过爬虫相关文章,出门左拐,我的个人博客中。 123456789101112131415161718192021222324252627282930313233343536373839404142/*Navicat MySQL Data TransferSource Server : 本地Source Server Version : 50610Source Host : localhost:3306Source Database : crawlerTarget Server Type : MYSQLTarget Server Version : 50610File Encoding : 65001Date: 2017-01-22 22:40:13*/SET FOREIGN_KEY_CHECKS=0;-- ------------------------------ Table structure for beautiful_pictures-- ----------------------------DROP TABLE IF EXISTS `beautiful_pictures`;CREATE TABLE `beautiful_pictures` ( `id` varchar(255) NOT NULL COMMENT '美女图片爬取', `title` varchar(255) DEFAULT NULL, `url` varchar(255) DEFAULT NULL, `pictureurls_num` int(11) DEFAULT NULL, `zan` int(11) DEFAULT NULL, `biaoqian` varchar(255) DEFAULT NULL, `keywords` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ------------------------------ Table structure for picture-- ----------------------------DROP TABLE IF EXISTS `picture`;CREATE TABLE `picture` ( `id` varchar(255) NOT NULL COMMENT '每张图片的地址', `pictures_id` varchar(255) DEFAULT NULL, `url` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8; 整合框架的搭建新建项目添加配置文件 我这里使用的IDE工具是Spring Tool Suite,spring开发的首选开发工具 新建一个maven project ,选择maven-archetypr-webapp这个文件结构模版。 配置pom.xml文件和application.properties文件 pom.xml 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\"> <modelVersion>4.0.0</modelVersion> <groupId>com.baomidou</groupId> <artifactId>mybatisplus-spring-boot</artifactId> <packaging>war</packaging> <version>1.0</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.4.1.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.1.1</version> </dependency> <dependency> <groupId>Mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.38</version> </dependency> <!-- mybatisPlus代码生成模板引擎 --> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity</artifactId> </dependency> <!-- druid阿里巴巴数据库连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.26</version> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> </dependency> <!-- MP 核心库 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus</artifactId> <version>2.0.1</version> </dependency> <!-- JUnit test dependency --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <dependency> <groupId>com.jayway.restassured</groupId> <artifactId>rest-assured</artifactId> <version>2.3.3</version> <scope>test</scope> </dependency> <!-- fastjson阿里巴巴jSON处理器 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.13</version> </dependency> <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.10.2</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project> application.properties 1234567891011121314151617181920212223242526272829303132#viewspring.mvc.view.prefix=/WEB-INF/view/spring.mvc.view.suffix=.jsp# jdbc_configspring.datasource.url=jdbc:mysql://127.0.0.1:3306/crawler?characterEncoding=utf8spring.datasource.username=rootspring.datasource.password=spring.datasource.driver-class-name=com.mysql.jdbc.Driverspring.datasource.type=com.alibaba.druid.pool.DruidDataSource#druid_configspring.datasource.max-active: 20spring.datasource.initial-size: 1spring.datasource.min-idle: 3spring.datasource.max-wait: 60000spring.datasource.time-between-eviction-runs-millis: 60000spring.datasource.min-evictable-idle-time-millis: 300000spring.datasource.test-while-idle: truespring.datasource.test-on-borrow: falsespring.datasource.test-on-return: falsespring.datasource.poolPreparedStatements: true# mybatis_configmybatis.mapper-locations=classpath:io/z77z/mapper/*Mapper.xml mybatis.typeAliasesPackage=io.z77z.entity# log_config DEBUG ERROR INFO WARN logging.level.root=WARNlogging.file=./logs/spring-boot-logging.log 如果pom.xml报错,大部分原因都是因为jar包没有下载成功,可以手动下载后放到maven本地仓库里面,阿里maven镜像仓库链接:http://maven.aliyun.com/nexus/#welcome 编写MybatisPlusConfig.java 对mybatisplus的一些配置,配置成Bean交给spring容器管理 12345678910111213141516171819202122232425262728293031323334353637383940414243444546@Beanpublic PaginationInterceptor paginationInterceptor() { PaginationInterceptor page = new PaginationInterceptor(); page.setDialectType(\"mysql\"); return page;}/** * 这里全部使用mybatis-autoconfigure 已经自动加载的资源。不手动指定 * 配置文件和mybatis-boot的配置文件同步 * @return */@Beanpublic MybatisSqlSessionFactoryBean mybatisSqlSessionFactoryBean() { MybatisSqlSessionFactoryBean mybatisPlus = new MybatisSqlSessionFactoryBean(); mybatisPlus.setDataSource(dataSource); mybatisPlus.setVfs(SpringBootVFS.class); if (StringUtils.hasText(this.properties.getConfigLocation())) { mybatisPlus.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation())); } mybatisPlus.setConfiguration(properties.getConfiguration()); if (!ObjectUtils.isEmpty(this.interceptors)) { mybatisPlus.setPlugins(this.interceptors); } // MP 全局配置,更多内容进入类看注释 GlobalConfiguration globalConfig = new GlobalConfiguration(); globalConfig.setDbType(DBType.MYSQL.name()); // ID 策略 AUTO->`0`(\"数据库ID自增\") INPUT->`1`(用户输入ID\") ID_WORKER->`2`(\"全局唯一ID\") UUID->`3`(\"全局唯一ID\") globalConfig.setIdType(3); mybatisPlus.setGlobalConfig(globalConfig); MybatisConfiguration mc = new MybatisConfiguration(); mc.setDefaultScriptingLanguage(MybatisXMLLanguageDriver.class); mybatisPlus.setConfiguration(mc); if (this.databaseIdProvider != null) { mybatisPlus.setDatabaseIdProvider(this.databaseIdProvider); } if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) { mybatisPlus.setTypeAliasesPackage(this.properties.getTypeAliasesPackage()); } if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) { mybatisPlus.setTypeHandlersPackage(this.properties.getTypeHandlersPackage()); } if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) { mybatisPlus.setMapperLocations(this.properties.resolveMapperLocations()); } return mybatisPlus;} 使用mybatisplus的代码生成插件MpGenerator.java 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596/** * <p> * 代码生成器演示 * </p> */public class MpGenerator { /** * <p> * MySQL 生成演示 * </p> */ public static void main(String[] args) { AutoGenerator mpg = new AutoGenerator(); // 全局配置 GlobalConfig gc = new GlobalConfig(); gc.setOutputDir(\"C://\"); gc.setFileOverride(true); gc.setActiveRecord(true); gc.setEnableCache(false);// XML 二级缓存 gc.setBaseResultMap(true);// XML ResultMap gc.setBaseColumnList(false);// XML columList gc.setAuthor(\"z77z\"); // 自定义文件命名,注意 %s 会自动填充表实体属性! // gc.setMapperName(\"%sDao\"); // gc.setXmlName(\"%sDao\"); // gc.setServiceName(\"MP%sService\"); // gc.setServiceImplName(\"%sServiceDiy\"); // gc.setControllerName(\"%sAction\"); mpg.setGlobalConfig(gc); // 数据源配置 DataSourceConfig dsc = new DataSourceConfig(); dsc.setDbType(DbType.MYSQL); dsc.setDriverName(\"com.mysql.jdbc.Driver\"); dsc.setUsername(\"root\"); dsc.setPassword(\"\"); dsc.setUrl(\"jdbc:mysql://127.0.0.1:3306/crawler?characterEncoding=utf8\"); mpg.setDataSource(dsc); // 策略配置 StrategyConfig strategy = new StrategyConfig(); //strategy.setTablePrefix(\"beautiful_\");// 此处可以修改为您的表前缀 strategy.setNaming(NamingStrategy.underline_to_camel);// 表名生成策略 strategy.setInclude(new String[] { \"beautiful_pictures\" }); // 需要生成的表 strategy.setInclude(new String[] { \"picture\" }); // 需要生成的表 // strategy.setExclude(new String[]{\"test\"}); // 排除生成的表 // 字段名生成策略 strategy.setFieldNaming(NamingStrategy.underline_to_camel); // 自定义实体父类 // strategy.setSuperEntityClass(\"com.baomidou.demo.TestEntity\"); // 自定义实体,公共字段 // strategy.setSuperEntityColumns(new String[] { \"test_id\", \"age\" }); // 自定义 mapper 父类 // strategy.setSuperMapperClass(\"com.baomidou.demo.TestMapper\"); // 自定义 service 父类 // strategy.setSuperServiceClass(\"com.baomidou.demo.TestService\"); // 自定义 service 实现类父类 // strategy.setSuperServiceImplClass(\"com.baomidou.demo.TestServiceImpl\"); // 自定义 controller 父类 // strategy.setSuperControllerClass(\"com.baomidou.demo.TestController\"); // 【实体】是否生成字段常量(默认 false) // public static final String ID = \"test_id\"; // strategy.setEntityColumnConstant(true); // 【实体】是否为构建者模型(默认 false) // public User setName(String name) {this.name = name; return this;} // strategy.setEntityBuliderModel(true); mpg.setStrategy(strategy); // 包配置 PackageConfig pc = new PackageConfig(); pc.setParent(\"io\"); pc.setModuleName(\"z77z\"); mpg.setPackageInfo(pc); // 注入自定义配置,可以在 VM 中使用 cfg.abc 设置的值 InjectionConfig cfg = new InjectionConfig() { @Override public void initMap() { Map<String, Object> map = new HashMap<String, Object>(); map.put(\"abc\", this.getConfig().getGlobalConfig().getAuthor() + \"-mp\"); this.setMap(map); } }; mpg.setCfg(cfg); // 自定义模板配置,可以 copy 源码 mybatis-plus/src/main/resources/template 下面内容修改, // 放置自己项目的 src/main/resources/template 目录下, 默认名称一下可以不配置,也可以自定义模板名称 // TemplateConfig tc = new TemplateConfig(); // tc.setController(\"...\"); // tc.setEntity(\"...\"); // tc.setMapper(\"...\"); // tc.setXml(\"...\"); // tc.setService(\"...\"); // tc.setServiceImpl(\"...\"); // mpg.setTemplate(tc); // 执行生成 mpg.execute(); // 打印注入设置 System.err.println(mpg.getCfg().getMap().get(\"abc\")); }} 新建上面java文件,注释已经解释的很清楚了,这里就不多说,配置好后直接运行,就会得到相应模块代码,直接将其稍作修改放到项目中即可。 对插件生成的代码简要分析就拿生成的mapper文件做说明: 生成的mapper文件 如上图所示生成的mapper继承了一个类,是mybatisplus提供的,查看其源码可以发现,继承的类里面封装了一些常用的通用的增删改查的代码,还有对分页查询的处理。简化了开发的代码量,只需要专注于业务逻辑的编写和实现,源码如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207/** * <p> * Mapper 继承该接口后,无需编写 mapper.xml 文件,即可获得CRUD功能 * </p> * <p> * 这个 Mapper 支持 id 泛型 * </p> * * @author hubin * @Date 2016-01-23 */public interface BaseMapper<T> { /** * <p> * 插入一条记录 * </p> * * @param entity * 实体对象 * @return int */ Integer insert(T entity); /** * <p> * 根据 ID 删除 * </p> * * @param id * 主键ID * @return int */ Integer deleteById(Serializable id); /** * <p> * 根据 columnMap 条件,删除记录 * </p> * * @param columnMap * 表字段 map 对象 * @return int */ Integer deleteByMap(@Param(\"cm\") Map<String, Object> columnMap); /** * <p> * 根据 entity 条件,删除记录 * </p> * * @param wrapper * 实体对象封装操作类(可以为 null) * @return int */ Integer delete(@Param(\"ew\") Wrapper<T> wrapper); /** * <p> * 删除(根据ID 批量删除) * </p> * * @param idList * 主键ID列表 * @return int */ Integer deleteBatchIds(List<? extends Serializable> idList); /** * <p> * 根据 ID 修改 * </p> * * @param entity * 实体对象 * @return int */ Integer updateById(T entity); /** * <p> * 根据 whereEntity 条件,更新记录 * </p> * * @param entity * 实体对象 * @param wrapper * 实体对象封装操作类(可以为 null) * @return */ Integer update(@Param(\"et\") T entity, @Param(\"ew\") Wrapper<T> wrapper); /** * <p> * 根据 ID 查询 * </p> * * @param id * 主键ID * @return T */ T selectById(Serializable id); /** * <p> * 查询(根据ID 批量查询) * </p> * * @param idList * 主键ID列表 * @return List<T> */ List<T> selectBatchIds(List<? extends Serializable> idList); /** * <p> * 查询(根据 columnMap 条件) * </p> * * @param columnMap * 表字段 map 对象 * @return List<T> */ List<T> selectByMap(@Param(\"cm\") Map<String, Object> columnMap); /** * <p> * 根据 entity 条件,查询一条记录 * </p> * * @param entity * 实体对象 * @return T */ T selectOne(@Param(\"ew\") T entity); /** * <p> * 根据 Wrapper 条件,查询总记录数 * </p> * * @param wrapper * 实体对象 * @return int */ Integer selectCount(@Param(\"ew\") Wrapper<T> wrapper); /** * <p> * 根据 entity 条件,查询全部记录 * </p> * * @param wrapper * 实体对象封装操作类(可以为 null) * @return List<T> */ List<T> selectList(@Param(\"ew\") Wrapper<T> wrapper); /** * <p> * 根据 Wrapper 条件,查询全部记录 * </p> * * @param wrapper * 实体对象封装操作类(可以为 null) * @return List<T> */ List<Map<String, Object>> selectMaps(@Param(\"ew\") Wrapper<T> wrapper); /** * <p> * 根据 Wrapper 条件,查询全部记录 * </p> * * @param wrapper * 实体对象封装操作类(可以为 null) * @return List<Object> */ List<Object> selectObjs(@Param(\"ew\") Wrapper<T> wrapper); /** * <p> * 根据 entity 条件,查询全部记录(并翻页) * </p> * * @param rowBounds * 分页查询条件(可以为 RowBounds.DEFAULT) * @param wrapper * 实体对象封装操作类(可以为 null) * @return List<T> */ List<T> selectPage(RowBounds rowBounds, @Param(\"ew\") Wrapper<T> wrapper); /** * <p> * 根据 Wrapper 条件,查询全部记录(并翻页) * </p> * * @param rowBounds * 分页查询条件(可以为 RowBounds.DEFAULT) * @param wrapper * 实体对象封装操作类 * @return List<Map<String, Object>> */ List<Map<String, Object>> selectMapsPage(RowBounds rowBounds, @Param(\"ew\") Wrapper<T> wrapper);} 对于其他的生成文件大家可以依照这个思路去查看其源码,结合之前传统的SSM开发,了解其原理。 创建springboot入口Application.java 需要注意的地方我都写在注释里面了,这里我是把爬虫的启动也写在这个配置文件里面了,项目一启动就会执行爬虫。知道爬取的数据为空就停止爬取。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293/** * springboot * * @author z77z * */// 扫描指定包下面的mapper接口@MapperScan(\"io.z77z.dao\")// 该 @SpringBootApplication 注解等价于以默认属性使用:// @Configuration// @EnableAutoConfiguration// @ComponentScan@SpringBootApplicationpublic class Application extends SpringBootServletInitializer implements CommandLineRunner { @Autowired BeautifulPicturesService beautifulPicturesService; @Autowired PictureService pictureService; //入口 public static void main(String[] args) { SpringApplication.run(Application.class, args); } //Java EE应用服务器配置, //如果要使用tomcat来加载jsp的话就必须继承SpringBootServletInitializer类并且重写其中configure方法 @Override protected SpringApplicationBuilder configure (SpringApplicationBuilder application) { return application.sources(Application.class); } //springboot运行后此方法首先被调用 //实现CommandLineRunner抽象类中的run方法 @Override public void run(String... args) throws Exception { //返回值 int result = 1; //访问页码 Integer page = 1; //启动爬虫 System.out.println(\"爬虫开始工作!\"); while(result==1){ result = crawler(page.toString()); page+=1; if(result==0){ System.out.println(\"爬虫运行结束!!\"); } } } public int crawler(String page){ //初始化返回值 int result = 1; //网站首页地址 String homeUrl = \"http://www.87g.com/\"; //接口地址 String url = \"http://www.87g.com/index.php?m=content&c=content_ajax&a=picture_page&siteid=1&catid=35&page=\"+page; System.out.println(\"当前爬取第\"+ page +\"页数据\"); //访问接口, JSONObject resultjson = CrawlerUtil.getReturnJson(url); if(resultjson!=null){ //获取其value值 Collection<Object> jsonList = resultjson.values(); for(Object obj : jsonList){ BeautifulPictures beautifulPictures = JSON.parseObject(obj.toString(), BeautifulPictures.class); String Keywords = beautifulPictures.getKeywords(); //按map条件查询。判断是否已经爬过,没有就入库 Map<String, Object> map = new HashMap<String, Object>(); map.put(\"keywords\", Keywords); int cont = beautifulPicturesService.selectByMap(map).size(); if(cont==0){ //入库 beautifulPicturesService.insert(beautifulPictures); //访问链接获取document,并保存里面的图片 List<Picture> listPicture = CrawlerUtil.getArticleInfo(homeUrl+beautifulPictures.getUrl(),beautifulPictures); for(Picture picture : listPicture){ //入库 pictureService.insert(picture); } }else{ System.out.println(homeUrl+beautifulPictures.getUrl()+\"页面数据已经爬过了!!\"); } } }else{ System.out.println(\"爬取到\"+page+\"页时没有数据了!!\"); result = 0; } return result; }} 编写测试jsp和controller12345678910111213<%@ page language=\"java\" contentType=\"text/html; charset=UTF-8\" pageEncoding=\"UTF-8\"%><!DOCTYPE html><html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"><title>Insert title here</title></head><body> helloJsp <hr>${user}</body></html> 123456789101112131415//如果是要返回jsp页面就必须要使用@Controller而不是@RestController@Controller@RequestMapping("/test")public class TestController { @Autowired BeautifulPicturesService beautifulPicturesService; @RequestMapping("/test1") public String view(Model model,Page<BeautifulPictures> page) { Page<BeautifulPictures> pageList= beautifulPicturesService.selectPage(page); model.addAttribute("user",JSON.toJSONString(pageList.getRecords())); return "index"; }} 运行直接运行Application.java启动项目,项目会运行在springboot内嵌的web容器中,本项目是使用的内嵌tomcat容器。运行成功后,在浏览器输入http://localhost:8080/test/test1?current=2&size=10(curren代表要获取的页码,size代表要获取的数据条数)就会出现下面效果: 这里写图片描述","categories":[],"tags":[{"name":"springboot","slug":"springboot","permalink":"http://z77z.oschina.io/tags/springboot/"},{"name":"爬虫","slug":"爬虫","permalink":"http://z77z.oschina.io/tags/爬虫/"},{"name":"mybatisplus","slug":"mybatisplus","permalink":"http://z77z.oschina.io/tags/mybatisplus/"}]},{"title":"免费个人博客搭建教程(详细-图文)--Hexo+OSChina","slug":"免费个人博客搭建教程(详细-图文)--Hexo+OSChina","date":"2017-01-14T09:49:58.000Z","updated":"2017-01-14T14:09:26.591Z","comments":true,"path":"2017/01/14/免费个人博客搭建教程(详细-图文)--Hexo+OSChina/","link":"","permalink":"http://z77z.oschina.io/2017/01/14/免费个人博客搭建教程(详细-图文)--Hexo+OSChina/","excerpt":"","text":"本人作为一个屌丝程序员,年少无为,卖马为生,买不起服务器,买不起域名,但是又想拥有属于自己的博客网站,那就只有通过技术来实现这一切了。先上成果:点击,现在我把我自己搭建博客的过程共享出来,只要你按照步骤一步步走下去,一定会搭建成功,如果大家在搭建过程中遇到什么问题,欢迎在我的博客评论区留言,也欢迎大神进来,教我做码,带我装逼,带我飞。博客地址,JUST DO IT(离开舒适区)。 使用工具介绍 码云 Pages:码云 Pages 是一个免费的静态网页托管服务,您可以使用码云 Pages 托管博客、项目官网等静态网页。这样就不用购买服务器和域名了,如果您使用过 Github Pages 那么您会很快上手使用码云的Pages服务。这里使用码云上的Pages 而不是用Github Pages,主要原因就是在国内没有VPN的话。。。你懂得!还有就是支持国产!链接:https://git.oschina.net/ Hexo:Hexo是一个快速、简洁且高效的博客框架。Hexo使用 Markdown(或其他渲染引擎)解析文章,在几秒内,即可利用靓丽的主题生成静态网页。使用者只需要专注于写博客而不需要关注网站是如何生成的。而且还有丰富的博客主题可以选择!链接:https://hexo.io/zh-cn/(ps:这是国外网站,翻墙吧少年,需要自由门 翻墙软件可以在博客中联系我哦) 搭建环境 Node.js安装。 参考:http://www.runoob.com/nodejs/nodejs-install-setup.html(ps:如果没有VPN的用户最好切换一下npm的源,不然后续的步骤可能会让你想砸电脑,安装完后,在命令窗口执行下面代码) 1npm config set registry http://registry.cnpmjs.org #切换npm为淘宝镜像 Git安装。 参考http://www.runoob.com/git/git-install-setup.html Hexo安装。( ps:安装完Node.js和Git之后不需要配置些什么,只需要安装成功就是了,如果想明白原理的话可以深入的学习。) 如果您的电脑中已经安装上述必备程序,那么恭喜您!接下来只需要使用 npm 即可完成 Hexo 的安装。打开命令窗口输入下面代码: 1npm install -g hexo-cli 安装成功后输入hexo 如果得到下面这个结果,恭喜你!安装成功! Hexo的常用命令说明: 参考:https://hexo.io/zh-cn/docs/commands.html 本地运行Hexo安装 Hexo 完成后,请依次执行下列命令,Hexo 将会在指定文件夹中新建所需要的文件。将<folder> 替换成项目存放的文件夹目录,1234hexo init <folder>cd <folder>npm installhexo generate 新建完成后,指定文件夹的目录如下: 1234567891011.├── .deploy #需要部署的文件├── node_modules #Hexo插件├── public #生成的静态网页文件├── scaffolds #模板├── source #博客正文和其他源文件, 404 favicon CNAME 等都应该放在这里| ├── _drafts #草稿| └── _posts #文章├── themes #主题├── _config.yml #全局配置文件└── package.json 进行到这步后就可以先在本地运行下,看看效果了。执行下面命令: 123cd <folder> #切换到项目目录下npm install #install before start blogginghexo server #运行本地服务 浏览器输入http://localhost:4000就可以看到效果。如下: 默认配置运行效果 修改Hexo的主题当然,上面运行的只是Hexo官方默认的主题配置效果,想要个性一点?,下面我们就将他改造成自己想要的个性主题。 Hexo的主题都是一些前端民间高手写的模版,可以在官方收录的主题中去挑选:https://hexo.io/themes/ 看上一个主题后点击下图位置,进入下载主题文件。如下图: 主题下载完成后,将主题文件解压到Hexo项目的thems文件夹下面(ps:我这里的项目目录是在桌面的Hexo文件夹里面。) 接下来就是修改Hexo的配置文件_config.yml,将里面theme 对应的值改为之前下载的主题的文件夹名字,本文里面下载的主题文件夹名字为hexo-theme-smackdown-master。 修改前 修改前 修改后 修改后 注意:这里“:”后面必须要有一个空格,而且这个空格要在英文输入法下,不然会报一些稀奇古怪的错。 按照之前步骤本地重新运行后,如下图: 改变主题后效果 关于Hexo中_config.yml 文件的其他配置,大家可以参考官方的文档,这里就不多赘述了: 文档链接 https://hexo.io/zh-cn/docs/configuration.html 下载的每个主题中,有一个配置文件,名字也叫_config.yml,这里大家千万不要和之前Hexo根目录下的_config.yml搞混淆了,这里面配置的主要是些与主题相关的东西,比如一些文章阅读量,多说插件,cnzz站长工具等等的配置信息。一般里面也都有注释,这里就不赘述了,不懂得可以在我的博客问我。 一些主题在GitHub上面也都有主题安装的一些文档,写的都很详细。推荐一个主题的文档,结合官方的文档看完之后,基本上也就明白了。 推荐一个主题的文档https://github.com/yscoder/hexo-theme-indigo/wiki 编写博客文章如果你使用过MarkDown 来写博客文章的话,接下来就简单多了,没使用过也没关系,推荐大家一个编辑工具马克飞象 链接:https://maxiang.io/,在编辑器里面写好文章后,复制或另存为.md文件, 与普通的.md文件不同 要在文件开头添加下面代码 12345title: #文章标题date: #文章日期tags: #文章标签categories: #文章分类--- 在.md文件的开头添加上面代码,是为了让Hexo框架在生成网页的时候,设置相应的参数。例如下图所示: 将写好的.md文件放入Hexo项目的source 目录下的_posts 文件夹中,可以看到里面有个hello-world.md 文件,这就是默认的文章。 重新在本地启动项目,访问就可以看到之前添加的文章。 生成静态Html文件现在只能在本地启动项目然后通过本地地址访问博客网站,下面我们就可以利用Hexo生成静态Html,很简单,只需要在命令窗口执行下面代码: 12cd <folder> #切换到项目目录下hexo generate #生成静态文件到项目根目录的public文件夹中 发布静态Html文件到码云 Pages上注册一个码云帐号,并创建一个项目。如下图: 创建完项目后得到项目的Https的地址后面要用。地址获取如下图 这里使用git将项目中public 文件夹下的文件管理起来,并推送到码云上。 这里借用一个插件来帮助我们完成,安装 hexo-deployer-git。安装代码如下: 1npm install hexo-deployer-git --save 配置项目根目录_config.yml 文件,修改deploy 的值,如下图: 修改前 修改前 修改后,注意repo 的地址是之前在码云上面创建醒目后获取的地址。 修改后 修改完后在命令窗口执行下面命令: 12cd <folder> #切换到项目目录下hexo deploy #一键部署功能 之后会弹出一个对话框,输入码云的帐号密码。 部署成功之后,登录码云,查看之前创建的项目中出现了public 文件夹中的文件,这时候代表之前的部署是成功的。 然后如下图,启动码云的pages功能: 访问链接: 这时候就看到之前和本地启动一样的效果了。博客部署完成,在这之后,只需要每次用马克飞象写好文章后,放入Hexo项目的source 目录下的_posts 文件夹中,在按照之前步骤更新博客就OK了。还可以随时切换博客的主题哦!","categories":[],"tags":[{"name":"博客","slug":"博客","permalink":"http://z77z.oschina.io/tags/博客/"},{"name":"Hexo","slug":"Hexo","permalink":"http://z77z.oschina.io/tags/Hexo/"},{"name":"码云","slug":"码云","permalink":"http://z77z.oschina.io/tags/码云/"}]},{"title":"通讯录之按汉字首字母排序-java","slug":"通讯录之按汉字首字母排序-java","date":"2017-01-05T02:49:58.000Z","updated":"2017-01-05T14:56:36.420Z","comments":true,"path":"2017/01/05/通讯录之按汉字首字母排序-java/","link":"","permalink":"http://z77z.oschina.io/2017/01/05/通讯录之按汉字首字母排序-java/","excerpt":"","text":"最近开发手机端OA系统通讯录时遇到了用汉字首字母排序的问题,各种谷歌后发现了一个轮子pinyin4J,这个轮子是可以将汉字转换成字母拼音,个人觉得很好用,完美的解决了排序的问题,分享一下。 原博客地址 一.工具介绍 pinyin4j是一个支持将简体和繁体中文转换到成拼音的Java开源类库,作者是Li Min ([email protected])。 支持同一汉字有多个发音 还支持拼音的格式化输出,比如第几声之类的, 同时支持简体中文、繁体中文转换为拼音…使用起来也非常简单。下面是其官方网址,其中提供了下载: *pinyin4j的官方下载地址[http://sourceforge.net/projects/pinyin4j/files/] 本文只是使用了pinyin4J的主要功能,还有更多更好耍的功能,大家可以去研究官网文档。哈哈 下载解压后的目录结构及说明如下 (1) doc : pinyin4j的api文档 (2) lib : pinyin4j的jar包 (3) src: pinyin4j的源代码 (4) CHANGELOG.txt : pinyin4j的版本更新日志 (5) COPYING.txt : LICENSE说明 (6) README.txt : pinyin4j的概要介绍 二.转换代码12345678910111213141516171819202122232425262728293031// 获取汉字的首字母大写public String getFirstSpell(String string) {StringBuffer pybf = new StringBuffer();char[] arr = string.toCharArray();HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);for (int i = 0; i < arr.length; i++) { if (arr[i] > 128) { //如果已经是字母就不用转换了 try { //获取当前汉字的全拼 String[] temp = PinyinHelper.toHanyuPinyinStringArray( arr[i], defaultFormat); if (temp != null) { pybf.append(temp[0].charAt(0));// 取首字母 } } catch (BadHanyuPinyinOutputFormatCombination e) { e.printStackTrace(); } } else { if (arr[i] >= 'a' && arr[i] <= 'z') { arr[i] -= 32; } /*if (arr[0] >= 'A' && arr[0] <= 'Z') {// 将大写转换为小写 arr[0] += 32; }*/ pybf.append(arr[i]); }}return pybf.toString();} 三.实现 Comparable 接口并且使用内部类compare重载compareTo方法实现排序代码。12345678Collections.sort(list, new Comparator<Map<String, Object>>() { public int compare(Map<String, Object> o1, Map<String, Object> o2) { return getFirstSpellOne((String) o1.get(\"str\")) .compareTo( getFirstSpellOne((String) o2 .get(\"str\"))); }}); 四.测试效果 测试效果 五.那么问题来了这个列子只能按字符串第一个字符的首字母来排序,比如字符串张三和赵五第一个字符首字母是一样的,那能不能再通过第二个字符三和五来进行排序呐。如果字符串的第二个字符首字母也是一样的呐,依次类推。希望哪位大神好的思路交流交流,带我装逼带我飞。。。 六.JUST DO IT。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。","categories":[],"tags":[{"name":"java","slug":"java","permalink":"http://z77z.oschina.io/tags/java/"},{"name":"排序","slug":"排序","permalink":"http://z77z.oschina.io/tags/排序/"},{"name":"pinyin4J","slug":"pinyin4J","permalink":"http://z77z.oschina.io/tags/pinyin4J/"}]},{"title":"js封装from表单数据为json串进行ajax提交","slug":"js封装from表单数据为json串进行ajax提交","date":"2017-01-02T12:42:58.000Z","updated":"2017-01-05T14:51:05.833Z","comments":true,"path":"2017/01/02/js封装from表单数据为json串进行ajax提交/","link":"","permalink":"http://z77z.oschina.io/2017/01/02/js封装from表单数据为json串进行ajax提交/","excerpt":"","text":"json封装代码 123456789101112131415function getFormJson(frm) { //frm:form表单的id var o = {}; var a = $(\"#\"+frm).serializeArray(); $.each(a, function() { if (o[this.name] !== undefined) { if (!o[this.name].push) { o[this.name] = [ o[this.name] ]; } o[this.name].push(this.value || ''); } else { o[this.name] = this.value || ''; } }); return o; } 返回的数据格式为标准的json格式,ajax使用如下: 12345678$.ajax({ type: 'post', url: 'your url', data: getFormJson(frm), success: function(data) { // your code }});","categories":[],"tags":[{"name":"javascript","slug":"javascript","permalink":"http://z77z.oschina.io/tags/javascript/"},{"name":"json","slug":"json","permalink":"http://z77z.oschina.io/tags/json/"},{"name":"ajax","slug":"ajax","permalink":"http://z77z.oschina.io/tags/ajax/"}]},{"title":"java-spring-mybatis整合实现爬虫之《今日头条》搞笑动态图片爬取","slug":"java-spring-mybatis整合实现爬虫之《今日头条》搞笑动态图片爬取","date":"2016-12-29T12:42:58.000Z","updated":"2017-01-05T13:16:18.197Z","comments":true,"path":"2016/12/29/java-spring-mybatis整合实现爬虫之《今日头条》搞笑动态图片爬取/","link":"","permalink":"http://z77z.oschina.io/2016/12/29/java-spring-mybatis整合实现爬虫之《今日头条》搞笑动态图片爬取/","excerpt":"","text":"一.此爬虫介绍 今日头条本身就是做爬虫的,爬取各大网站的图片文字信息,再自己整合后推送给用户,特别是里面的动态图片,很有意思。在网上搜了搜,大多都是用Python来写的,本人是学习javaweb这块的,对正则表达式也不是很熟悉,就想着能不能换个我熟悉的方式来写。此爬虫使用spring+mybatis框架整合实现,使用mysql数据库保存爬取的数据,用jsoup来操作HTML的标签节点(完美避开正则表达式),获取文章中动态图片的链接,通过响应头中“Content-Type”的值来判断图片的格式,再将图片保存在本地。当然也可以爬取里面的文字,比如一些搞笑的黄段子,在此基础上稍加改动就可以实现,此爬虫只是提供一个入门的思路,更多好玩的爬虫玩法还待大家去开发,哈哈。 先上效果图抓取的动态图: 数据库: 二.技术选型 核心语言:java; 核心框架:spring; 持久层框架:mybatis; 数据库连接池:Alibaba Drui; 日志管理:Log4j; jar包管理:maven; 。。。。 三.找规律,划重点 打开头条首页,找到点击搞笑模块,点击F12,下滚后加载下一页,发现是通过ajax请求api来获取的数据,如下图: 这是响应的json数据,里面的参数和值顾名思义大家都懂得。 是ajax访问就好解决了,通过我百度谷歌各种研究后发现,ajax请求的前三个参数是不变的,改变category参数是请求不同的模块,本列子是请求的搞笑模块所以值为funny,max_behot_time和max_behot_time_tmp这两个参数值是时间戳,首次请求是0,之后的值是响应json数据里面的next中的值。as和cp值是通过一段js生成的,其实就是一个加密了的时间戳而已。js代码后面会贴。 四.开始搭框架撸代码 项目搭建后之后为下图所示的文件结构,不懂得自行谷歌 哈哈 不多说直接上核心代码了: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209public class TouTiaoCrawler { // 搞笑板块的api地址 public static final String FUNNY = \"http://www.toutiao.com/api/pc/feed/?utm_source=toutiao&widen=1\"; // 头条首页地址 public static final String TOUTIAO = \"http://www.toutiao.com\"; // 使用\"spring.xml\"和\"spring-mybatis.xml\"这两个配置文件创建Spring上下文 static ApplicationContext ac = new ClassPathXmlApplicationContext( \"spring-mybatis.xml\"); // 从Spring容器中根据bean的id取出我们要使用的funnyMapper对象 static FunnyMapper funnyMapper = (FunnyMapper) ac.getBean(\"funnyMapper\"); // 接口访问次数 private static int refreshCount = 0; // 时间戳 private static long time = 0; public static void main(String[] args) { System.out.println(\"----------开始干活!-----------------\"); while (true) { crawler(time); } } public static void crawler(long hottime) {// 传入时间戳,会获取这个时间戳的内容 refreshCount++; System.out.println(\"----------第\" + refreshCount + \"次刷新------返回的请求时间为:\" + hottime + \"----------\"); String url = FUNNY + \"&max_behot_time=\" + hottime + \"&max_behot_time_tmp=\" + hottime; JSONObject param = getUrlParam(); // 获取用js代码得到的as和cp的值 // 定义接口访问的模块 /* * __all__ : 推荐 news_hot: 热点 funny:搞笑 */ String module = \"funny\"; url += \"&as=\" + param.get(\"as\") + \"&cp=\" + param.get(\"cp\") + \"&category=\" + module; JSONObject json = null; try { json = getReturnJson(url);// 获取json串 } catch (Exception e) { e.printStackTrace(); } if (json != null) { time = json.getJSONObject(\"next\").getLongValue(\"max_behot_time\"); JSONArray data = json.getJSONArray(\"data\"); for (int i = 0; i < data.size(); i++) { try { JSONObject obj = (JSONObject) data.get(i); // 判断这条文章是否已经爬过 if (funnyMapper.selectByGroupId((String) obj .get(\"group_id\")) != null) { System.out .println(\"----------此文章已经爬过啦!-----------------\"); continue; } // 访问页面返回document对象 String url1 = TOUTIAO + \"/a\" + obj.getString(\"group_id\"); Document document = getArticleInfo(url1); System.out.println(\"----------成功访问了文章:\" + url1 + \"-----------------\"); // 将document也存入 obj.put(\"document\", document.toString()); // 将json对象转换成java Entity对象 Funny funny = JSON.parseObject(obj.toString(), Funny.class); // json入库 funny.setBehotTime(new Date()); funnyMapper.insertSelective(funny); } catch (Exception e) { e.printStackTrace(); } } } else { System.out.println(\"----------返回的json列表为空----------\"); } } // 访问接口,返回json封装的数据格式 public static JSONObject getReturnJson(String url) { try { URL httpUrl = new URL(url); BufferedReader in = new BufferedReader(new InputStreamReader( httpUrl.openStream(), \"UTF-8\")); String line = null; String content = \"\"; while ((line = in.readLine()) != null) { content += line; } in.close(); return JSONObject.parseObject(content); } catch (Exception e) { System.err.println(\"访问失败:\" + url); e.printStackTrace(); } return null; } // 获取网站的document对象 public static Document getArticleInfo(String url) { try { Connection connect = Jsoup.connect(url); Document document; document = connect.get(); Elements article = document.getElementsByClass(\"article-content\"); if (article.size() > 0) { Elements a = article.get(0).getElementsByTag(\"img\"); if (a.size() > 0) { for (Element e : a) { String url2 = e.attr(\"src\"); // 下载img标签里面的图片到本地 saveToFile(url2); } } } return document; } catch (IOException e) { System.err.println(\"访问文章页失败:\" + url + \" 原因\" + e.getMessage()); return null; } } // 执行js获取as和cp参数值 public static JSONObject getUrlParam() { JSONObject jsonObject = null; FileReader reader = null; try { ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName(\"javascript\"); String jsFileName = \"toutiao.js\"; // 读取js文件 reader = new FileReader(jsFileName); // 执行指定脚本 engine.eval(reader); if (engine instanceof Invocable) { Invocable invoke = (Invocable) engine; Object obj = invoke.invokeFunction(\"getParam\"); jsonObject = JSONObject.parseObject(obj != null ? obj .toString() : null); } } catch (Exception e) { e.printStackTrace(); } finally { try { if (reader != null) { reader.close(); } } catch (IOException e) { e.printStackTrace(); } } return jsonObject; } // 通过url获取图片并保存在本地 public static void saveToFile(String destUrl) { FileOutputStream fos = null; BufferedInputStream bis = null; HttpURLConnection httpUrl = null; URL url = null; String uuid = UUID.randomUUID().toString(); String fileAddress = \"d:\\\\imag/\" + uuid;// 存储本地文件地址 int BUFFER_SIZE = 1024; byte[] buf = new byte[BUFFER_SIZE]; int size = 0; try { url = new URL(destUrl); httpUrl = (HttpURLConnection) url.openConnection(); httpUrl.connect(); String Type = httpUrl.getHeaderField(\"Content-Type\"); if (Type.equals(\"image/gif\")) { fileAddress += \".gif\"; } else if (Type.equals(\"image/png\")) { fileAddress += \".png\"; } else if (Type.equals(\"image/jpeg\")) { fileAddress += \".jpg\"; } else { System.err.println(\"未知图片格式\"); return; } bis = new BufferedInputStream(httpUrl.getInputStream()); fos = new FileOutputStream(fileAddress); while ((size = bis.read(buf)) != -1) { fos.write(buf, 0, size); } fos.flush(); System.out.println(\"图片保存成功!地址:\" + fileAddress); } catch (IOException e) { e.printStackTrace(); } catch (ClassCastException e) { e.printStackTrace(); } finally { try { fos.close(); bis.close(); httpUrl.disconnect(); } catch (IOException e) { e.printStackTrace(); } catch (NullPointerException e) { e.printStackTrace(); } } }} 获取as和cp参数的js代码 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183function getParam(){ var asas; var cpcp; var t = Math.floor((new Date).getTime() / 1e3) , e = t.toString(16).toUpperCase() , i = md5(t).toString().toUpperCase(); if (8 != e.length){ asas = \"479BB4B7254C150\"; cpcp = \"7E0AC8874BB0985\"; }else{ for (var n = i.slice(0, 5), o = i.slice(-5), a = \"\", s = 0; 5 > s; s++){ a += n[s] + e[s]; } for (var r = \"\", c = 0; 5 > c; c++){ r += e[c + 3] + o[c]; } asas = \"A1\" + a + e.slice(-3); cpcp= e.slice(0, 3) + r + \"E1\"; } return '{\"as\":\"'+asas+'\",\"cp\":\"'+cpcp+'\"}';}!function(e) { \"use strict\"; function t(e, t) { var n = (65535 & e) + (65535 & t) , r = (e >> 16) + (t >> 16) + (n >> 16); return r << 16 | 65535 & n } function n(e, t) { return e << t | e >>> 32 - t } function r(e, r, o, i, a, u) { return t(n(t(t(r, e), t(i, u)), a), o) } function o(e, t, n, o, i, a, u) { return r(t & n | ~t & o, e, t, i, a, u) } function i(e, t, n, o, i, a, u) { return r(t & o | n & ~o, e, t, i, a, u) } function a(e, t, n, o, i, a, u) { return r(t ^ n ^ o, e, t, i, a, u) } function u(e, t, n, o, i, a, u) { return r(n ^ (t | ~o), e, t, i, a, u) } function s(e, n) { e[n >> 5] |= 128 << n % 32, e[(n + 64 >>> 9 << 4) + 14] = n; var r, s, c, l, f, p = 1732584193, d = -271733879, h = -1732584194, m = 271733878; for (r = 0; r < e.length; r += 16) s = p, c = d, l = h, f = m, p = o(p, d, h, m, e[r], 7, -680876936), m = o(m, p, d, h, e[r + 1], 12, -389564586), h = o(h, m, p, d, e[r + 2], 17, 606105819), d = o(d, h, m, p, e[r + 3], 22, -1044525330), p = o(p, d, h, m, e[r + 4], 7, -176418897), m = o(m, p, d, h, e[r + 5], 12, 1200080426), h = o(h, m, p, d, e[r + 6], 17, -1473231341), d = o(d, h, m, p, e[r + 7], 22, -45705983), p = o(p, d, h, m, e[r + 8], 7, 1770035416), m = o(m, p, d, h, e[r + 9], 12, -1958414417), h = o(h, m, p, d, e[r + 10], 17, -42063), d = o(d, h, m, p, e[r + 11], 22, -1990404162), p = o(p, d, h, m, e[r + 12], 7, 1804603682), m = o(m, p, d, h, e[r + 13], 12, -40341101), h = o(h, m, p, d, e[r + 14], 17, -1502002290), d = o(d, h, m, p, e[r + 15], 22, 1236535329), p = i(p, d, h, m, e[r + 1], 5, -165796510), m = i(m, p, d, h, e[r + 6], 9, -1069501632), h = i(h, m, p, d, e[r + 11], 14, 643717713), d = i(d, h, m, p, e[r], 20, -373897302), p = i(p, d, h, m, e[r + 5], 5, -701558691), m = i(m, p, d, h, e[r + 10], 9, 38016083), h = i(h, m, p, d, e[r + 15], 14, -660478335), d = i(d, h, m, p, e[r + 4], 20, -405537848), p = i(p, d, h, m, e[r + 9], 5, 568446438), m = i(m, p, d, h, e[r + 14], 9, -1019803690), h = i(h, m, p, d, e[r + 3], 14, -187363961), d = i(d, h, m, p, e[r + 8], 20, 1163531501), p = i(p, d, h, m, e[r + 13], 5, -1444681467), m = i(m, p, d, h, e[r + 2], 9, -51403784), h = i(h, m, p, d, e[r + 7], 14, 1735328473), d = i(d, h, m, p, e[r + 12], 20, -1926607734), p = a(p, d, h, m, e[r + 5], 4, -378558), m = a(m, p, d, h, e[r + 8], 11, -2022574463), h = a(h, m, p, d, e[r + 11], 16, 1839030562), d = a(d, h, m, p, e[r + 14], 23, -35309556), p = a(p, d, h, m, e[r + 1], 4, -1530992060), m = a(m, p, d, h, e[r + 4], 11, 1272893353), h = a(h, m, p, d, e[r + 7], 16, -155497632), d = a(d, h, m, p, e[r + 10], 23, -1094730640), p = a(p, d, h, m, e[r + 13], 4, 681279174), m = a(m, p, d, h, e[r], 11, -358537222), h = a(h, m, p, d, e[r + 3], 16, -722521979), d = a(d, h, m, p, e[r + 6], 23, 76029189), p = a(p, d, h, m, e[r + 9], 4, -640364487), m = a(m, p, d, h, e[r + 12], 11, -421815835), h = a(h, m, p, d, e[r + 15], 16, 530742520), d = a(d, h, m, p, e[r + 2], 23, -995338651), p = u(p, d, h, m, e[r], 6, -198630844), m = u(m, p, d, h, e[r + 7], 10, 1126891415), h = u(h, m, p, d, e[r + 14], 15, -1416354905), d = u(d, h, m, p, e[r + 5], 21, -57434055), p = u(p, d, h, m, e[r + 12], 6, 1700485571), m = u(m, p, d, h, e[r + 3], 10, -1894986606), h = u(h, m, p, d, e[r + 10], 15, -1051523), d = u(d, h, m, p, e[r + 1], 21, -2054922799), p = u(p, d, h, m, e[r + 8], 6, 1873313359), m = u(m, p, d, h, e[r + 15], 10, -30611744), h = u(h, m, p, d, e[r + 6], 15, -1560198380), d = u(d, h, m, p, e[r + 13], 21, 1309151649), p = u(p, d, h, m, e[r + 4], 6, -145523070), m = u(m, p, d, h, e[r + 11], 10, -1120210379), h = u(h, m, p, d, e[r + 2], 15, 718787259), d = u(d, h, m, p, e[r + 9], 21, -343485551), p = t(p, s), d = t(d, c), h = t(h, l), m = t(m, f); return [p, d, h, m] } function c(e) { var t, n = \"\"; for (t = 0; t < 32 * e.length; t += 8) n += String.fromCharCode(e[t >> 5] >>> t % 32 & 255); return n } function l(e) { var t, n = []; for (n[(e.length >> 2) - 1] = void 0, t = 0; t < n.length; t += 1) n[t] = 0; for (t = 0; t < 8 * e.length; t += 8) n[t >> 5] |= (255 & e.charCodeAt(t / 8)) << t % 32; return n } function f(e) { return c(s(l(e), 8 * e.length)) } function p(e, t) { var n, r, o = l(e), i = [], a = []; for (i[15] = a[15] = void 0, o.length > 16 && (o = s(o, 8 * e.length)), n = 0; 16 > n; n += 1) i[n] = 909522486 ^ o[n], a[n] = 1549556828 ^ o[n]; return r = s(i.concat(l(t)), 512 + 8 * t.length), c(s(a.concat(r), 640)) } function d(e) { var t, n, r = \"0123456789abcdef\", o = \"\"; for (n = 0; n < e.length; n += 1) t = e.charCodeAt(n), o += r.charAt(t >>> 4 & 15) + r.charAt(15 & t); return o } function h(e) { return unescape(encodeURIComponent(e)) } function m(e) { return f(h(e)) } function g(e) { return d(m(e)) } function v(e, t) { return p(h(e), h(t)) } function y(e, t) { return d(v(e, t)) } function b(e, t, n) { return t ? n ? v(t, e) : y(t, e) : n ? m(e) : g(e) } \"function\" == typeof define && define.amd ? define(\"static/js/lib/md5\", [\"require\"], function() { return b }) : \"object\" == typeof module && module.exports ? module.exports = b : e.md5 = b}(this) 五.最后 我还发现了头条有个简约版,研究后发现这个简约版应该更好爬一些。 访问的格式是p+页码,直接读取每页里面的链接,就可以进行爬取了,就不再通过json串来获取文章地址,也不需要传什么限制参数,在本项目上稍加改动就可以了 六.JUST DO IT 。。。。。。。。。。。。。。。。。。。。。。","categories":[],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"http://z77z.oschina.io/tags/爬虫/"},{"name":"java","slug":"java","permalink":"http://z77z.oschina.io/tags/java/"},{"name":"spring","slug":"spring","permalink":"http://z77z.oschina.io/tags/spring/"},{"name":"mybatis","slug":"mybatis","permalink":"http://z77z.oschina.io/tags/mybatis/"}]},{"title":"Hello World","slug":"hello-world","date":"2016-12-28T13:42:58.000Z","updated":"2016-12-30T17:00:47.177Z","comments":true,"path":"2016/12/28/hello-world/","link":"","permalink":"http://z77z.oschina.io/2016/12/28/hello-world/","excerpt":"","text":"欢迎使用马克飞象@(示例笔记本)[马克飞象|帮助|Markdown] 马克飞象是一款专为印象笔记(Evernote)打造的Markdown编辑器,通过精心的设计与技术实现,配合印象笔记强大的存储和同步功能,带来前所未有的书写体验。特点概述: 功能丰富 :支持高亮代码块、LaTeX 公式、流程图,本地图片以及附件上传,甚至截图粘贴,工作学习好帮手; 得心应手 :简洁高效的编辑器,提供桌面客户端以及离线Chrome App,支持移动端 Web; 深度整合 :支持选择笔记本和添加标签,支持从印象笔记跳转编辑,轻松管理。 [TOC] Markdown简介 Markdown 是一种轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档,然后转换成格式丰富的HTML页面。 —— 维基百科 正如您在阅读的这份文档,它使用简单的符号标识不同的标题,将某些文字标记为粗体或者斜体,创建一个链接或一个脚注[^demo]。下面列举了几个高级功能,更多语法请按Ctrl + /查看帮助。 代码块12345678910@requires_authorizationdef somefunc(param1='', param2=0): '''A docstring''' if param1 > param2: # interesting print 'Greater' return (param2 - param1 + 1) or Noneclass SomeClass: pass>>> message = '''interpreter... prompt''' LaTeX 公式可以创建行内公式,例如 $\\Gamma(n) = (n-1)!\\quad\\forall n\\in\\mathbb N$。或者块级公式: $$ x = \\dfrac{-b \\pm \\sqrt{b^2 - 4ac}}{2a} $$ 表格 Item Value Qty Computer 1600 USD 5 Phone 12 USD 12 Pipe 1 USD 234 流程图12345678st=>start: Starte=>endop=>operation: My Operationcond=>condition: Yes or No?st->op->condcond(yes)->econd(no)->op 以及时序图: 123Alice->Bob: Hello Bob, how are you?Note right of Bob: Bob thinksBob-->Alice: I am good thanks! 提示:想了解更多,请查看流程图语法以及时序图语法。 复选框使用 - [ ] 和 - [x] 语法可以创建复选框,实现 todo-list 等功能。例如: [x] 已完成事项 [ ] 待办事项1 [ ] 待办事项2 注意:目前支持尚不完全,在印象笔记中勾选复选框是无效、不能同步的,所以必须在马克飞象中修改 Markdown 原文才可生效。下个版本将会全面支持。 印象笔记相关笔记本和标签马克飞象增加了@(笔记本)[标签A|标签B]语法, 以选择笔记本和添加标签。 绑定账号后, 输入(自动会出现笔记本列表,请从中选择。 笔记标题马克飞象会自动使用文档内出现的第一个标题作为笔记标题。例如本文,就是第一行的 欢迎使用马克飞象。 快捷编辑保存在印象笔记中的笔记,右上角会有一个红色的编辑按钮,点击后会回到马克飞象中打开并编辑该笔记。 注意:目前用户在印象笔记中单方面做的任何修改,马克飞象是无法自动感知和更新的。所以请务必回到马克飞象编辑。 数据同步马克飞象通过将Markdown原文以隐藏内容保存在笔记中的精妙设计,实现了对Markdown的存储和再次编辑。既解决了其他产品只是单向导出HTML的单薄,又规避了服务端存储Markdown带来的隐私安全问题。这样,服务端仅作为对印象笔记 API调用和数据转换之用。 隐私声明:用户所有的笔记数据,均保存在印象笔记中。马克飞象不存储用户的任何笔记数据。 离线存储马克飞象使用浏览器离线存储将内容实时保存在本地,不必担心网络断掉或浏览器崩溃。为了节省空间和避免冲突,已同步至印象笔记并且不再修改的笔记将删除部分本地缓存,不过依然可以随时通过文档管理打开。 注意:虽然浏览器存储大部分时候都比较可靠,但印象笔记作为专业云存储,更值得信赖。以防万一,请务必经常及时同步到印象笔记。 编辑器相关设置右侧系统菜单(快捷键Ctrl + M)的设置中,提供了界面字体、字号、自定义CSS、vim/emacs 键盘模式等高级选项。 快捷键帮助 Ctrl + /同步文档 Ctrl + S创建文档 Ctrl + Alt + N最大化编辑器 Ctrl + Enter预览文档 Ctrl + Alt + Enter文档管理 Ctrl + O系统菜单 Ctrl + M 加粗 Ctrl + B插入图片 Ctrl + G插入链接 Ctrl + L提升标题 Ctrl + H 关于收费马克飞象为新用户提供 10 天的试用期,试用期过后需要续费才能继续使用。未购买或者未及时续费,将不能同步新的笔记。之前保存过的笔记依然可以编辑。 反馈与建议 微博:@马克飞象,@GGock 邮箱:hustgock@gmail.com 感谢阅读这份帮助文档。请点击右上角,绑定印象笔记账号,开启全新的记录与分享体验吧。 [^demo]: 这是一个示例脚注。请查阅 MultiMarkdown 文档 关于脚注的说明。 限制: 印象笔记的笔记内容使用 ENML 格式,基于 HTML,但是不支持某些标签和属性,例如id,这就导致脚注和TOC无法正常点击。","categories":[],"tags":[{"name":"Hello World","slug":"Hello-World","permalink":"http://z77z.oschina.io/tags/Hello-World/"}]}]}