经过了不知道多少个小时的coding,姑且算是把代码大约实现结束了,由于某些原因不得不入门全栈开发。虽然之前有过全栈开发的经验,但是都是基于python后端,本次使用java做后台还是遇到了不小的麻烦。简单梳理一下我认为我们组做的好与不好的地方吧。
0. 代码复用性
本次代码重写了后台,可以说是极大的提升了工作量,由于原本的代码实现计算功能几乎是重写了一个编译器,难度太高,因此决定改用二叉树作为数据结构,将符号与数字作为节点插入树中,这样只要节点排布不相同,所遍历出的树也就不相同了,可以说是将查重问题和计算问题同时解决了。值得一提的是,这样的数据结构的好处是通过中序遍历,以上两个问题可以很高效的解决,但是也暴露出了之前代码复用性太差的问题,偷懒不可取呀,每一次的程序都应该认真设计,构思后,再开始动手,从根本上提升程序的可扩展性。应当在之后的开发中引以为戒,简单附一下数据结构:
1 package group_work; 2 3 public class Node { 4 Node m_node_left; 5 Node m_node_right; 6 int m_op_num; 7 String m_operate; 8 private All get; 9 10 String symbol_double[] = new String[] {"+","-","*","/"}; 11 String symbol_single[] = new String[] {"^0.5","^2","sin","cos","tan"}; 12 13 boolean is_oper = false;//false 表示是数字 true表示是操作符 14 boolean is_single = false; // false 表示是双操作数 true表示是单操作数 15 16 public Node(int num,String oper) 17 { 18 // TODO Auto-generated constructor stub 19 this.m_op_num = num; 20 this.m_operate = oper; 21 } 22 23 24 public void display() 25 { 26 if (is_oper) { 27 System.out.print(this.m_operate); 28 } 29 else { 30 System.out.print(this.m_op_num); 31 } 32 } 33 34 public String toString() 35 { 36 String mem_temp; 37 if (is_oper) { 38 mem_temp = this.m_operate; 39 } 40 else { 41 mem_temp = String.valueOf(this.m_op_num); 42 } 43 return mem_temp; 44 } 45 46 public static Node Insert(Node root,String te,int num) //建树 47 { 48 if(root == null) 49 { 50 root = new Node(num--,te); 51 } 52 else if(root.is_oper) 53 { 54 if(root.m_node_left == null) 55 { 56 root.m_node_left = Insert(root.m_node_left,te,num--); 57 } 58 else if(root.m_node_right == null && !root.is_single) 59 { 60 root.m_node_right = Insert(root.m_node_right,te,num--); 61 } 62 } 63 return root; 64 } 65 66 }
1 package group_work; 2 3 public class BinTree { 4 Node root; 5 int status; 6 static private All get; 7 8 static String symbol_double[] = new String[] {"+","-","*","/"}; 9 static String symbol_single[] = new String[] {"^0.5","^2","sin","cos","tan"}; 10 11 BinTree(int status) { 12 // TODO Auto-generated constructor stub 13 root.m_node_left = null; 14 root.m_node_right = null; 15 this.status = status; 16 } 17 18 public BinTree() { 19 // TODO Auto-generated constructor stub 20 } 21 22 int symFlag = 0; //sum为最后得到的结果 23 double sum = 0,operNow = 0; 24 25 public static String CreateSymbol(int status) 26 { 27 String opera; 28 if(status == 1) 29 { 30 int num = get.RandSel(0,4); 31 opera = symbol_double[num]; 32 } 33 34 else if(status == 2) 35 { 36 int num = get.RandSel(0,6); 37 if(num < 4) 38 { 39 opera = symbol_double[num]; 40 } 41 else 42 { 43 num -= 4; 44 opera = symbol_single[num]; 45 } 46 } 47 48 else 49 { 50 int num = get.RandSel(0,7); 51 if(num < 4) 52 { 53 opera = symbol_double[num]; 54 } 55 else 56 { 57 num -= 2; 58 opera = symbol_single[num]; 59 } 60 } 61 return opera; 62 } 63 64 public Node CreateTree(Node root,int begin,int end) 65 { 66 Node.Insert(root,CreateSymbol(status),get.RandSel(begin, end)); 67 return root; 68 } 69 public void TravleInOrder(Node root) 70 { 71 if (root.m_node_left != null) 72 { 73 TravleInOrder(root.m_node_left); 74 } 75 76 if(!root.is_oper) //如果为数字则已是根节点,返回 77 { 78 operNow = root.m_op_num; 79 if(sum == 0) //赋予初始值 80 { 81 sum = operNow; 82 } 83 } 84 85 else //若为符号,则获取对应编号值以进行后续操作得到运算结果 86 { 87 88 if(root.m_operate == "+") 89 { 90 symFlag = 1; 91 } 92 else if(root.m_operate == "-") 93 { 94 symFlag = 2; 95 } 96 else if(root.m_operate == "*") 97 { 98 symFlag = 3; 99 } 100 else if(root.m_operate == "/") 101 { 102 symFlag = 4; 103 } 104 else if(root.m_operate == "sin") 105 { 106 operNow = Math.sin(operNow); 107 } 108 else if(root.m_operate == "cos") 109 { 110 operNow = Math.cos(operNow); 111 } 112 else if(root.m_operate == "tan") 113 { 114 operNow = Math.tan(operNow); 115 } 116 else if(root.m_operate == "^0.5") 117 { 118 operNow = Math.sqrt(operNow); 119 } 120 else if(root.m_operate == "^2") 121 { 122 operNow = operNow * operNow; 123 } 124 } 125 126 if (root.m_node_right != null) 127 { 128 TravleInOrder(root.m_node_right); 129 } 130 131 if(symFlag != 0) 132 { 133 if(symFlag == 1) 134 { 135 sum += operNow; 136 } 137 else if(symFlag == 2) 138 { 139 sum -= operNow; 140 } 141 else if(symFlag == 3) 142 { 143 sum *= operNow; 144 } 145 else if(symFlag == 4) 146 { 147 sum /= operNow; 148 } 149 symFlag = 0; 150 } 151 } 152 153 154 }
1. 短信接口
这次开发中的一个重难点是使用短信接口,最初由于签名问题一直难以直接申请到Key,在同学的帮助下终于将key的问题解决。表示感谢!
这里涉及到一个实时验证以及前后端交互的问题。我们分开两个部分来开。首先是实时验证部分,这一部分主要是对form当中的input进行实时校验,那么显然需要利用js对其缓存区进行监听。由于手机号都是以固定格式,十一位数字组成,那么这里显然可以利用正则表达式来对其进行检索判断,同时需要注意的是,为了避免用户不停点击发送按钮,导致短信API在一段时间内多次调用影响体验,这里要对button进行禁用,通过setInterval来进行定时器调用,对该函数每隔1秒render一次,这样就可以动态提醒用户还有多少秒可以重新进行发送,提升了用户体验。
为了保证页面的美观度,我选择了使用bootstrap来进行响应式布局,以适配移动端。同时,使用jQuery做粘合,保证代码的简洁性。
按钮按下后,会优先检查手机号输入是否合法,若不合法会提示重新输入,合法会发送验证码,同时ban掉button。如果验证码与原值匹配,后端返回值为真,则进行下一步跳转,这里演示一下输入的检查。
附,表单验证与按钮禁用代码。
1 var phoneReg = /(^1[3|4|5|7|8]\d{9}$)|(^09\d{8}$)/;//手机号正则 2 var count = 60; //间隔函数,1秒执行 3 var InterValObj1; //timer变量,控制时间 4 var curCount1;//当前剩余秒数 5 /*第一*/ 6 function sendMessage1() { 7 curCount1 = count; 8 var phone = $.trim($('#phone1').val()); 9 if (!phoneReg.test(phone)) { 10 alert(" 请输入有效的手机号码"); 11 return false; 12 } 13 //设置button效果,开始计时 14 $("#btnSendCode1").attr("disabled", "true"); 15 $("#btnSendCode1").val( + curCount1 + "秒再获取"); 16 InterValObj1 = window.setInterval(SetRemainTime1, 1000); //启动计时器,1秒执行一次 17 //向后台发送处理数据 18 19 } 20 function SetRemainTime1() { 21 if (curCount1 == 0) { 22 window.clearInterval(InterValObj1);//停止计时器 23 $("#btnSendCode1").removeAttr("disabled");//启用按钮 24 $("#btnSendCode1").val("重新发送"); 25 } 26 else { 27 curCount1--; 28 $("#btnSendCode1").val( + curCount1 + "秒再获取"); 29 } 30 } 31 32 /*提交*/ 33 function binding(){ 34 alert(1) 35 }
2. 后台
后台当中主要是利用jsp与前端的ajax数据传输,这里要提到一个概念,叫做 服务器-客户端 模式。现在的网页中大多数的数据交互都是通过发送请求与响应请求完成的,那么研究其中的过程就对代码的构建尤为重要。刚刚我们说到,在html页面所包含的表单中会向后台传送数据,这里实际上就是提交了一个请求,也就是触发了submit事件,当服务器(后端)收到这个请求后,通过response应答这个请求,同时回传数据给前端,这样数据就成功的在这两端进行了交互。
那么我们先来看看前端的ajax干了什么。ajax(Asynchronous Javascript And XML)实质上就是异步JavaScript与XML,这里尤其要注意一点,ajax是异步操作!异步操作!异步的意思就是,他没有你想象中那么美好,并不是真的在顺序执行的。这也是我在另一个项目中遇到的问题,由于它是异步操作,有时候你很难判断数据和下一步操作究竟哪个能先到达,有时候很奇异的是你ajax所回传的数据可能正是你前端要先load进来才能采取下一步操作的数据,那么如果这个时候数据还没拿到是不是就凉了呢?有一种解决办法就是通过回调函数来进行解决。所谓回调函数实际上就是当一个函数执行完成后所采取的行动,有严格意义上的顺序关系。那么这样就可以锁定操作的顺序了,把另外函数作为函数指针传入,ajax返回的数据作为参数,就可以实现我们所想要的目标效果,这里给出一个简单的demo。
1 function getNameList(callback) { 2 $.ajax({ 3 url:"/get_person", 4 data:{}, 5 dataType:"json" 6 }) 7 .done(function(data_p){ 8 callback(data_p) 9 data_all = data_p 10 }) 11 } 12 13 window.onload = function () { 14 getNameList(init) 15 }
其他关于ajax的知识,其实大多都是计算机网络的基本知识,详情参考API,并不在此赘述。
接下来我们看看后端,刚刚提到说后端需要接受请求,那么这时候参数的对应就显得较为重要。通常有两种数据传输的方式,GET方式与POST方式,其中获取参数我们通常使用request.getParameter("amount")这样的方式,其中的参数表示需要获取的键,这里有键自然能获得其相应的值。
比如这里我们实现的题目总数输入中,这段代码就很好的诠释了以上的概念:
1 public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 2 // 语言设置 3 response.setCharacterEncoding("UTF-8"); 4 response.setContentType("text/html;charset=UTF-8"); 5 6 // 数目设置 7 amount = Integer.valueOf(request.getParameter("amount")); 8 for (int i = 0; i < amount; i++) { 9 calList[i].status = status; 10 calList[i].CreateTree(calList[i].root, 0, 5); 11 } 12 } 13 14 public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 15 doGet(request, response); 16 } 17 18 }
以上是我们组的合作中的重点与难点,后续可能还会有更新。