1 编写MVC框架
服务器启动时,会加载application.properties配置文件,配置文件中有多个类,配置文件是由HandlerMapping来加载。
HandlerMapping根据配置文件中的每个类,获取到每个类的方法,获取到方法之后再循环遍历这些方法,再找每个带有ResponseBody或ResponseView的方法,再把这些方法都存放在HandlerMapping中,之后用于处理给用户响应,所以服务器启动之后HandlerMapping中会存一堆方法。
当用户去请求DispatcherServlet时,首先去HandlerMapping中找有没有处理这个请求的方法,没有的话抛404异常。
请求servlet的路径是:*.do
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <servlet> <servlet-name>DispatcherServlet</servlet-name> <servlet-class>com.wangjiawei.mvc.DispatcherServlet</servlet-class> <!-- 启动的时候加载配置文件 --> <init-param> <param-name>contentConfigLocation</param-name> <param-value>application.properties</param-value> </init-param> <load-on-startup>0</load-on-startup> </servlet> <servlet-mapping> <servlet-name>DispatcherServlet</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> </web-app>
1.1 定义两个注解
package com.wangjiawei.mvc; import java.lang.annotation.*; /** * 2 * @Author: 小王同学 * 3 * @Date: 2020/9/30 9:56 * 4 被此注解添加的方法,会被用于处理请求 * 5 方法返回的内容,会以文字形式返回到客户端 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ResponseBody { String value(); }
package com.wangjiawei.mvc; import java.lang.annotation.*; /** * 2 * @Author: 小王同学 * 3 * @Date: 2020/9/30 9:56 * 4 被此注解添加的方法,会被用于处理请求 * 5 方法返回的内容,会直接重定向 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ResponseView { String value(); }
再定义枚举类对应上面的两个注解:
package com.wangjiawei.mvc; /** * 2 * @Author: 小王同学 * 3 * @Date: 2020/9/30 10:01 * 4 响应的类型是文字还是视图 */ public enum ResponseType { TEXT,VIEW; }
1.2 编写映射器 HandlerMapping
package com.wangjiawei.mvc; import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Properties; /** * 2 * @Author: 小王同学 * 3 * @Date: 2020/9/30 10:04 * 4 映射器,包含了大量的网址与方法的对应关系 */ public class HandlerMapping { private static Map<String, MVCMapping> data = new HashMap<>(); public static MVCMapping get(String uri){ return data.get(uri); } public static void load(InputStream is){ Properties ppt = new Properties(); try { ppt.load(is); } catch (IOException e) { e.printStackTrace(); } // 获取配置文件中描述的每个类 Collection<Object> values = ppt.values(); for (Object value : values) { String className = (String)value; try { // 加载配置文件中描述的每个类(用于处理请求的类) Class c = Class.forName(className); // 创建类的对象 Object obj = c.getConstructor().newInstance(); // 获取类的所有方法 Method[] methods = c.getMethods(); for (Method method : methods) { Annotation[] as = method.getAnnotations(); if (as != null){ for (Annotation annotation : as){ if (annotation instanceof ResponseBody){ // 此方法用于返回字符串给客户端 MVCMapping mvcMapping = new MVCMapping(obj, method, ResponseType.TEXT); Object o = data.put(((ResponseBody) annotation).value(), mvcMapping); if (o != null){ // 存在了重复的请求地址 throw new RuntimeException("请求地址重复" + ((ResponseBody) annotation).value()); } }else if(annotation instanceof ResponseView){ // 此方法用于返回界面给客户端 MVCMapping mvcMapping = new MVCMapping(obj, method, ResponseType.VIEW); Object o = data.put(((ResponseView) annotation).value(), mvcMapping); if (o != null){ // 存在了重复的请求地址 throw new RuntimeException("请求地址重复" + ((ResponseView) annotation).value()); } } } } } } catch (Exception e) { e.printStackTrace(); } } } /** * 映射对象,每一个对象封装了一个方法,用于处理请求 */ public static class MVCMapping{ private Object obj; private Method method; private ResponseType type; public MVCMapping() { } public MVCMapping(Object obj, Method method, ResponseType type) { this.obj = obj; this.method = method; this.type = type; } public Object getObj() { return obj; } public void setObj(Object obj) { this.obj = obj; } public Method getMethod() { return method; } public void setMethod(Method method) { this.method = method; } public ResponseType getType() { return type; } public void setType(ResponseType type) { this.type = type; } } }
1.3 定义分发Servlet
package com.wangjiawei.mvc; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Properties; /** * 2 * @Author: 小王同学 * 3 * @Date: 2020/9/29 21:22 * 4 */ public class DispatcherServlet extends HttpServlet { @Override public void init(ServletConfig config) throws ServletException { String path = config.getInitParameter("contentConfigLocation"); InputStream is = DispatcherServlet.class.getClassLoader().getResourceAsStream(path); HandlerMapping.load(is); } @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 获取用户请求的uri String uri = req.getRequestURI(); HandlerMapping.MVCMapping mvcMapping = HandlerMapping.get(uri); if (mvcMapping == null){ resp.sendError(404, "MVC:映射地址不存在" + uri); return; } Object obj = mvcMapping.getObj(); Method method = mvcMapping.getMethod(); Object result = null; try { result = method.invoke(obj, req, resp); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } switch (mvcMapping.getType()){ case TEXT: resp.getWriter().write((String) result); break; case VIEW: resp.sendRedirect((String) result); break; default: break; } } }
1.4 测试类
package com.wangjiawei.test; import com.sun.deploy.net.HttpResponse; import com.wangjiawei.mvc.ResponseBody; import com.wangjiawei.mvc.ResponseView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 2 * @Author: 小王同学 * 3 * @Date: 2020/9/30 11:08 * 4 */ public class UserController { @ResponseBody("/login.do") public String login(HttpServletRequest request, HttpServletResponse response){ return "login success"; } @ResponseView("/reg.do") public String reg(HttpServletRequest request, HttpServletResponse response){ return "success.jsp"; } }
2 前端补充
2.1 二维码生成
步骤:
1.引入Jquery.js文件
2.引入jquery.qrcode.js文件
3.引入支持中文的编码js文件 (utf.js)
4.在网页中编写一个div 用于显示二维码
<div id="div1"></div>
5.准备二维码的规格对象(JSON)
var config = { width:数字,//值是number类型, 表示的单位是px 必须传递 height:数字,//值是number类型, 表示的单位是px 必须传递 text:"内容",//text就表示二维码中存储的数据 必须传递 correctLevel:数字,//取值为0|1|2|3 表示二维码的纠错级别0:L/1:M/2:Q/3:H ,默认0 可选参数 background:"#rrggbb",//默认白色, 表示二维码的后景颜色 可选参数 foreground:"#rrggbb",//默认黑色, 表示二维码的前景颜色 可选参数 render:"绘制模式"//取值:table/canvas , 默认table 可选参数 };
通过选择器, 查找到上述的div ,得到Jquery对象, 通过jquery对象的qrcode函数生成二维码
$("#div1").qrcode(config);
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>二维码生成</title> <script src="js/jquery2.1.4.js"></script> <script src="js/jquery.qrcode.js"></script> <script src="js/utf.js"></script> </head> <body> <div id="qrcode"></div> <script> var config = { width:200, height:200, text:"欢迎关注【N小王日记】" }; $("#qrcode").qrcode(config); </script> </body> </html>
2.2 layui
2.2.1 布局部分
与BootStrap很像,就是展示一些界面效果。
2.2.2 弹出层
步骤:
1.下载layer , 并将解压后的layer文件夹 移动到项目中
2.引入jquery.js
3.引入layer.js
layer - msg函数
用于弹出信息提示框
格式1
layer.msg("文本");
格式2 抖动显示
layer.msg("文本",function(){ //弹窗结束后会执行 });
layer - load函数
格式1
弹出loading:
var index = layer.load(数字0-2); // 参数表示 loading的图表 //loading窗口在弹出时, 不允许进行操作.
关闭loading:
layer.close(index);
格式2
超时自动关闭的loading
var index = layer.load(数字0-2,{time:毫秒数字}) //在指定的毫秒后 ,如果没有使用layer.close关闭, 则自动关闭
layer - msg函数(load效果)
格式:
弹出的格式:
var index = layer.msg("文本",{icon:16,shade:0.01}) //因为是msg函数, 所以此窗口会自动消失
关闭的格式:
layer.close(index);
layer - alert函数 信息提示窗
格式:
layer.alert("文本内容",{icon:图片编号}); //图片编号: 0-16之间
layer - tips函数 提示层
格式:
layer.tips("文本内容","选择器",{tipsMore:true,tips:数字});
参数:
1.参数: tipsMore : 是否允许同时存在多个弹出的tips
2.参数: tips : 取值为数字1-4 , 分别表示弹出在元素的 上/右/下/左 . 默认弹出在右边
layer 所有弹出层的关闭
layer.closeAll();
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="js/jquery2.1.4.js"></script> <script src="layer/layer.js"></script> <script> function msgTest() { // layer.msg("提示的文字"); layer.msg("提示的文字", function () { // 窗口关系时执行,和上面的区别还有会添加一个抖动 }); } function loadTest() { // 返回弹出层的id var windowId = layer.load(0); // 0 - 16的数字,每一个代表不同效果 // 通过窗口id关闭窗口 setTimeout(function () { layer.close(windowId); }, 2000); } function msg_loadTest() { // icon不同的值对应不同的图标 layer.msg("提示的文字", {icon:16, shade:0.01}); } function alertTest() { layer.alert("文字内容", {icon : 10}); } function tipsTest() { // tipsMore表示 layer.tips("这里有秘密", "#s1", {tipsMore:true, tips:2}); } </script> </head> <body> <button onclick="msgTest()">msg函数(*)</button><br> <button onclick="loadTest()">load函数(*)</button><br> <button onclick="msg_loadTest()">load函数(load效果)</button><br> <button onclick="alertTest()">alert函数</button><br> <button onclick="tipsTest()">tips函数</button><br> <p> 锄禾日<span id="s1">当午</span>,汗滴禾下土 </p> </body> </html>
3 云短信使用
关键点:
1.在阿里云的sms控制台 创建签名
2.在阿里云的sms控制台 创建短信模板code值
3.在阿里云的RAM访问控制中心,新建一个用户 accessKeyId
4.在阿里云的RAM访问控制中心,新建一个用户 AccessKeySecret
《1》点击进入阿里云免费试用 , 然后登陆账号。
《2》 如图选择短信0元试用 (已经领取过的,直接走第三步即可。)
《3》点击进入阿里云短信官网 , 登陆状态下点击免费开通 (已开通的文字会替换为控制台 , 点击效果一样)
《4》选择国内消息
《5》创建短信签名
《6》创建短信模板
《7》创建子账户秘钥
鼠标移至右上角账户名称 选择accesskeys
选择开始使用子用户AccessKey
输入登陆名称和显示名称, 选择编程访问
复制得到的accessKeyId和 AccessKeySecret 留待后续使用
选择创建的子账户 ,点击添加权限
选择相应的权限, 并点击确定添加
《8》回到国内短信页面 , 复制审核成功的短信签名名称 以及 短信模板ID。
《9》完成上述步骤后, 已经得到了四个关键性参数
短信签名名称 : 快递e栈
短信模板CODE : SMS_20411…
accessKeyId:LTAI4GKsiFzUmLZ8…
AccessKeySecret: jUhIqqk3wwvhm2T1HOUVbC…
《9》下载jar文件 , 并引入到项目中
《10》将上述参数, 复制到如下代码中。 并通过JSON方式顺序填充每一个短信模板中到参数
import com.aliyuncs.CommonRequest; import com.aliyuncs.CommonResponse; import com.aliyuncs.DefaultAcsClient; import com.aliyuncs.IAcsClient; import com.aliyuncs.exceptions.ClientException; import com.aliyuncs.exceptions.ServerException; import com.aliyuncs.http.MethodType; import com.aliyuncs.profile.DefaultProfile; import com.google.gson.Gson; import java.util.HashMap; import java.util.Random; /** * 2 * @Author: 小王同学 * 3 * @Date: 2020/10/1 10:42 * 4 */ public class SMSDemo { public static void main(String[] args) { Random r = new Random(); int num = r.nextInt(900000) + 100000; send("18373110779", num + ""); } public static boolean send(String phoneNumber,String code) { DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", "LTAI4GKsiFzUmLZ8Qnx1Sfza", "jUhIqqk3wwvhm2T1HOUVbCmIOWNE0O"); IAcsClient client = new DefaultAcsClient(profile); CommonRequest request = new CommonRequest(); request.setSysMethod(MethodType.POST); request.setSysDomain("dysmsapi.aliyuncs.com"); request.setSysVersion("2017-05-25"); request.setSysAction("SendSms"); request.putQueryParameter("RegionId", "cn-hangzhou"); request.putQueryParameter("PhoneNumbers", phoneNumber); request.putQueryParameter("SignName", "快递e栈"); request.putQueryParameter("TemplateCode", "SMS_204115757"); request.putQueryParameter("TemplateParam", "{\"code\":\""+ code +"\"}"); try { CommonResponse response = client.getCommonResponse(request); System.out.println(response.getData()); String json = response.getData(); Gson g = new Gson(); HashMap result = g.fromJson(json, HashMap.class); if("OK".equals(result.get("Message"))) { return true; }else{ System.out.println("短信发送失败,原因:"+result.get("Message")); } } catch (ServerException e) { e.printStackTrace(); } catch (ClientException e) { e.printStackTrace(); } return false; } }
4 项目搭建
4.1 Druid
见下载资源
4.2 MVC
见 编写MVC框架 部分
4.3 时间格式化工具类
package com.wangjiawei.util; import java.text.SimpleDateFormat; import java.util.Date; /** * 2 * @Author: 小王同学 * 3 * @Date: 2020/10/1 11:30 * 4 */ public class DateFormatUtil { private static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static String format(Date date){ return format.format(date); } }
4.4 前后端用于通信的消息类
package com.wangjiawei.bean; /** * 2 * @Author: 小王同学 * 3 * @Date: 2020/10/1 12:36 * 4 */ public class Message { // {status:0, result:"", data:{}} /** * 状态码 0表示成功,-1表示失败 */ private int status; /** * 消息内容 */ private String result; /** 消息所携带的一组数据 */ private Object data; public Message() { } public Message(int status) { this.status = status; } public Message(String result) { this.result = result; } public Message(int status, String result, Object data) { this.status = status; this.result = result; this.data = data; } public String getResult() { return result; } public void setResult(String result) { this.result = result; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } public int getStatus() { return status; } public void setStatus(int status) { this.status = status; } }
4.5 随机数工具类
用于生成取件码
package com.wangjiawei.util; import java.util.Random; /** * 2 * @Author: 小王同学 * 3 * @Date: 2020/10/4 14:09 * 4 */ public class RandomUtil { private static Random r = new Random(); public static int getCode(){ return r.nextInt(900000) + 100000; } }
4.6 Json操作类
package com.wangjiawei.util; import com.google.gson.Gson; /** * 2 * @Author: 小王同学 * 3 * @Date: 2020/10/1 13:50 * 4 */ public class JSONUtil { private static Gson g = new Gson(); public static String toJSON(Object obj){ return g.toJson(obj); } }
5 管理员数据操作部分
后台管理前端页面模板:放在web.admin文件夹下
CREATE TABLE eadmin ( id INT PRIMARY KEY auto_increment, username VARCHAR ( 32 ), PASSWORD VARCHAR ( 32 ), loginip VARCHAR ( 32 ), logintime datetime, createtime datetime );
前台和后台交互的模式大致如下:
dao层接口:
package com.wangjiawei.dao; import java.util.Date; /** * 2 * @Author: 小王同学 * 3 * @Date: 2020/10/2 9:20 * 4 用于定义eadmin表的操作规范 */ public interface BaseAdminDao { /** * 根据用户名,更新登录时间和登录ip * @param username */ void updateLoginTime(String username, Date date, String ip); /** * 管理员根据账号密码登录 * @param username * @param password * @return 登录结果 true:登录成功 */ boolean login(String username, String password); }
dao层的实现类:
package com.wangjiawei.dao.imp; import com.wangjiawei.dao.BaseAdminDao; import com.wangjiawei.util.DruidUtil; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Date; /** * 2 * @Author: 小王同学 * 3 * @Date: 2020/10/2 9:26 * 4 */ public class AdminDaoMysql implements BaseAdminDao { private static final String SQL_UPDATE_LOGIN_TIME = "UPDATE EADMIN SET LOGINTIME = ?, LOGINIP = ? WHERE USERNAME = ?"; private static final String SQL_LOGIN = "SELECT ID FROM EADMIN WHERE USERNAME = ? AND PASSWORD = ?"; /** * 根据用户名,更新登录时间和登录ip * * @param username * @param date * @param ip */ @Override public void updateLoginTime(String username, Date date, String ip) { Connection connection = DruidUtil.getConnection(); PreparedStatement state = null; try { state = connection.prepareStatement(SQL_UPDATE_LOGIN_TIME); state.setDate(1, new java.sql.Date(date.getTime())); state.setString(2, ip); state.setString(3, username); state.executeUpdate(); } catch (SQLException throwables) { throwables.printStackTrace(); }finally { DruidUtil.close(connection,state,null); } } /** * 管理员根据账号密码登录 * * @param username * @param password * @return 登录结果 true:登录成功 */ @Override public boolean login(String username, String password) { Connection connection = DruidUtil.getConnection(); PreparedStatement state = null; ResultSet resultSet = null; try { state = connection.prepareStatement(SQL_LOGIN); state.setString(1,username); state.setString(2,password); resultSet = state.executeQuery(); // 如果能查出来则有,返回true return resultSet.next(); } catch (SQLException throwables) { throwables.printStackTrace(); }finally { DruidUtil.close(connection,state,null); } return false; } }
service层:
package com.wangjiawei.service; import com.wangjiawei.dao.BaseAdminDao; import com.wangjiawei.dao.imp.AdminDaoMysql; import java.util.Date; /** * 2 * @Author: 小王同学 * 3 * @Date: 2020/10/2 9:52 * 4 */ public class AdminService { private static BaseAdminDao dao = new AdminDaoMysql(); /** * 更新登陆时间与ip * @param username * @param date * @param ip */ public static void updateLoginTimeAndIp(String username, Date date, String ip){ dao.updateLoginTime(username, date, ip); } /** * 登录 * @param username * @param password * @return true:成功,false:失败 */ public static boolean login(String username, String password){ return dao.login(username, password); } }