一般项目结构都采用 MVC 的三层结构思想
M:model 实体类 (entity):一般一个实体类对应着数据库中的一张表
V: view 视图:显示数据,一般用于将从数据库中的数据展示到网页上
C:controller 控制层:控制器,用于处理输入,操作数据库
项目一般分为:
表现层:controller + jsp 调用 service 层
业务逻辑层:service 调用 DAO 层,并加入业务逻辑
数据访问层:DAO(data acess object) 对数据进行 增 删 改 查
实体类:entity 对应着数据库中的表
工具类:Utils 工具类,一般是一些数据库连接,对数据进行处理的工具类
例:
注意:
1) 一般不能跨层调用,只能是 controller ==》 service ==》dao
2) 层与层调用时,尽量使用接口,不要直接用实现类进行调用,提高扩展性,降低各层之间的 耦合性
菜单树的实现:
当一个列表 有多级目录时,这时就需要分多次查询了,第一次查询 一级目录,再根据 一级目录 去查询 二级目录,
当所有目录都查询出来,此时就形成了一个菜单树;
关键点: 使用 map<key , value> 存储 对应的多级目录, key: 对应菜单的 id(层级) , value:菜单的目录
/**
* DAO层: 从数据库中查询出所有的目录
*
/
public class MenuDao {
// 返回一个 菜单 的集合
public static List<Menu> selectAll(){
Connection conn = null;
PreparedStatement stat = null;
ResultSet rs = null;
List<Menu> menus = new ArrayList<>();
try {
conn = Utils.getConnection();
stat = conn.prepareStatement("select * from menu");
rs = stat.executeQuery();
while(rs.next()){
Menu m = new Menu();
m.setId(rs.getInt("id"));
m.setName(rs.getString("name"));
m.setPrefix(rs.getString("prefix"));
m.setSuffix(rs.getString("suffix"));
m.setSuffix_var(rs.getString("suffix_var"));
m.setPid(rs.getInt("pid"));
m.setHref(rs.getString("href"));
menus.add(m);
}
return menus;
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
Utils.close(conn,stat);
}
}
}
/**
* 对菜单数据进行操作
*
*/
public class MenuService {
/**
* 找到所有 一级目录 及 子目录
*/
public List<Menu> selectTop(){
// 查询所有菜单对象
List<Menu> menus = MenuDao.selectAll();
// top用来存储一级菜单
List<Menu> top = new ArrayList<>();
// Map: key存储菜单的id, value存储菜单对象
HashMap<Integer, Menu> map = new HashMap<>();
// 遍历all,找出其中的一级菜单
for (Menu menu : menus) {
if(menu.getPid()==0){
top.add(menu);
}
map.put(menu.getId(),menu);
}
//建立父子关系
for (Menu menu : menus) {
//根据pid找到上级目录
Menu parent = map.get(menu.getPid());
if(parent!=null){
// 将找到的子目录添加进该目录中
parent.getChildren().add(menu);
}
}
return top;
}
}
<%-- 在jsp 页面中显示菜单--%>
<ul>
<%--遍历一级目录--%>
<c:forEach items="${applicationScope.menus}" var="m1">
<li>
<a href="${m1.href}" > ${m1.prefix} <span class="menu-item-parent">${m1.name}</span> ${m1.suffix} </a>
<c:if test="${not empty m1.children}">
<ul>
<%--遍历二级目录--%>
<c:forEach items="${m1.children}" var="m2">
<li>
<a href="${m2.href}"> ${m2.prefix} <span class="menu-item-parent">${m2.name}</span>${m2.suffix}</a>
</li>
</c:forEach>
</ul>
</c:if>
</li>
</c:forEach>
</ul>
图片验证码的实现:
一般生成一张含有的验证码图片,然后进行登录,注册... 等操作的验证,提高程序的安全性
目的:
1)验证操作者是否是人为操作
2)防止表单重复提交
关键点:
1. 生成验证码图片,并显示在浏览器上:
1)使用 Random 随机生成 一串验证码
2)将生成的验证码 使用 java 中的 BufferedImage 类 手动画出一张包含验证码的图片
3)将生成的 图片对象 用 ImageIO.writer( 图片对象,"jepg|png", 响应字节输出流 );将 生成的图片 响应返回给浏览器
2. 验证 表单提交的验证码 是否正确
1)将刚开始生成的 那一串验证码(code) 存入session中的作用域(一次会话中有效)
req.getSession().setAttribute("code",code);
2) 获取表单提交的验证码,与 session 中的验证码进行验证
String str = req.getParamater("code");
String code = req.getSession().getAttribute("code");
判断 str.equals(code) 是否 == true, 若为 true ,则验证成功,反之,则验证失败
/**
* Utils : 生成验证码图片
* @author chen
*/
public class CheckCodeUtils {
private static String[] arr = {"a","b","c","d","e","f","g","h","i","j","k","m","n","o","p","q","r","s","t","u","v","w","x","y","z","0","1","2","3","4","5","6","7","8","9"};
//获取 4位 随机数
public static String random(){
Random r = new Random();
StringBuilder sb = new StringBuilder(4);
for (int i = 0; i < 4; i++) {
//产生随机数
int id = r.nextInt(arr.length);
sb.append(arr[id]);
}
return sb.toString();
}
// 将 4位 的验证码生成在一张图片上
// 输出流:将生成的图片以流的方式输出
public static void geImage(String str, OutputStream os ){
// 创建图片对象
BufferedImage image = new BufferedImage(100,80,BufferedImage.TYPE_INT_RGB);
// 获取画布
Graphics graphics = image.getGraphics();
// 作画
// 设置背景色,并填充
graphics.setColor(Color.white);
graphics.fillRect(0,0,100,80);
// 设置字体颜色
graphics.setColor(Color.BLACK);
// 设置字体
graphics.setFont(new Font("宋体",Font.PLAIN,40));
// 将 生成的字符串 写在画布上
graphics.drawString(str,0,40);
// 画上 干扰线
graphics.drawLine(0,0,100,80);
graphics.drawLine(0,100,0,80);
// 用输出流输出图片
try {
ImageIO.write(image,"png",os);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* Controller层: 生成登录验证码
* @author chen
*/
@WebServlet(urlPatterns = "/check.png")
public class CheckCodeImage extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("image/png");
// 生成一串随机数
String random = CheckCodeUtils.random();
// 将生成的随机数存入 session 作用域
HttpSession session = req.getSession();
session.setAttribute("str",random);
// 把图片当作响应流输出 resp.getOutStream()
CheckCodeUtils.geImage(random,resp.getOutputStream());
}
}
<%-- 在 JSP 中展示生成的验证码图片 --%>
<section>
<label class="label">验证码</label>
<label class="input">
<i class="icon-append fa fa-lock"></i>
<img src="/check.png" onclick="changeImage(this)">
<input type="text" name="checkcode">
</label>
</section>
页面显示:
短信验证码:
短信验证码需要调用 第三方短信服务 进行验证
例如: 阿里云的短信服务 https://www.aliyun.com/
产品和服务 => 云通信 => 短信服务
1) 充值
2) 下载短信的sdk (软件开发包 两个 jar 包)
3) 生成开发者的 id 和 secret
管理控制台 => 用户信息管理 => 创建 accessKeyId 和 accessKeySecret
4) 设置短信模板和短信签名
短信服务=>国内消息
短信签名:发送者是谁
短信模板:规定了短信的内容
/**
* 短信验证
*
*/
public class MessageTest {
//产品名称:云通信短信API产品,开发者无需替换
static final String product = "Dysmsapi";
//产品域名,开发者无需替换
static final String domain = "dysmsapi.aliyuncs.com";
// TODO 此处需要替换成开发者自己的AK(在阿里云访问控制台寻找)
static final String accessKeyId = "??????";
static final String accessKeySecret = "??????";
public static SendSmsResponse sendSms(String str) throws ClientException {
//可自助调整超时时间
System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
System.setProperty("sun.net.client.defaultReadTimeout", "10000");
//初始化acsClient,暂不支持region化
IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
IAcsClient acsClient = new DefaultAcsClient(profile);
//组装请求对象-具体描述见控制台-文档部分内容
SendSmsRequest request = new SendSmsRequest();
//必填:待发送手机号
request.setPhoneNumbers("?????");
//必填:短信签名-可在短信控制台中找到
request.setSignName("注册验证");
//必填:短信模板-可在短信控制台中找到
request.setTemplateCode("SMS_27925082"); // 尊敬的${name},您的验证码是${code},请在10分钟内输入有效
//可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为
request.setTemplateParam("\"name\":\"chen\",\"code\":str"); //
//选填-上行短信扩展码(无特殊需求用户请忽略此字段)
//request.setSmsUpExtendCode("90997");
//可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者
//request.setOutId("yourOutId");
//hint 此处可能会抛出异常,注意catch
SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
return sendSmsResponse;
}
public static void main(String[] args) throws ClientException {
System.out.println(CheckCode.random());
sendSms(CheckCode.random());
}
}
文件上传和下载:(以图片为例)
1) 表单的提交格式必须是 post
2) 必须采用复杂格式提交(多部分),不能是 参数名=参数值 的参数格式, 必须是multipart/form-data的格式
<form action="地址" method="post" enctype="multipart/form-data">
<input type="file" name="参数名"/>
</form>
文件上传和下载的两个 jar 包 :
文件上传:
/**
* 1: 导入 commons-fileupload-1.3.3.jar 和 commons-io-2.2.jar 两个jar包
*
* 2: 获取到一个 将上传文件存入服务器磁盘的 工具类
* DiskFileItemFactory factory = new DiskFileItemFactory();
*
* 再获取到一个 核心文件上传 的工具类
* ServletFileUpload servletFileUpload = new ServletFileUpload(factory);
*
* 3: 获取表单提交的多部分数据
* List<FileItem> fileItems = servletFileUpload.parseRequest(req);
*
* 4: 遍历集合,得到各部分数据
*/
@WebServlet(urlPatterns = "/upload")
public class FileUpLoad extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 将上传文件存入服务器磁盘的一个工具类
DiskFileItemFactory factory = new DiskFileItemFactory();
// 核心文件上传 工具类
ServletFileUpload servletFileUpload = new ServletFileUpload(factory);
try {
// 获取表单提交的多部分数据
List<FileItem> fileItems = servletFileUpload.parseRequest(req);
// 遍历表单数据
for (FileItem fileItem : fileItems) {
// 如果是普通的表单项
if(fileItem.isFormField()){
System.out.println("获取参数名:"+fileItem.getFieldName());
System.out.println("获取参数值:"+fileItem.getString("utf-8"));
}else{ // 文件数据(图片)
System.out.println("获取文件大小:"+fileItem.getSize());
if(fileItem.getSize()>0){
//将上传的文件保存到 本地磁盘 上的 G:\img 文件夹
fileItem.write(new File("G:\\img\\"+fileItem.getName()));
}
}
}
} catch (FileUploadException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
将上传的图片显示到页面上:
<%--在浏览器上显示上传的图片--%>
<img name="pic" src="images/${i.img}" height="200" width="200" >
/**
* 显示图片
* @author chen
*/
// 允许浏览器访问 /images 文件夹下的任意图片
@WebServlet(urlPatterns = "/images/*")
public class ProductImg extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1) 当响应的 content-type 设置为image,浏览器会显示图片
resp.setContentType("image/jpg");
// 把【浏览器的路径】 转换为 【服务器的磁盘路径】
// 获取浏览器实际输入路径
String uri = req.getRequestURI();
// 图片的磁盘路径
String path = "G:" + uri;
// 如果文件不存在,返回404
if( !new File(path).exists() ) {
resp.sendError(404);
return ;
}
// 如果存在,从文件输入流读取,向响应的输出流写
FileInputStream is = new FileInputStream(path);
OutputStream os = resp.getOutputStream();
IOUtils.copy(is, os);
IOUtils.closeQuietly(is);
}
}
文件下载:
// 用来显示服务器上,某个目录下任意的图片
@WebServlet(urlPatterns = "/img/*")
public class ImageServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1) 如果要把图片进行下载操作
resp.setContentType("application/octet-stream");
resp.addHeader("content-disposition","attachment;filename=1.jpg");
// 把【浏览器的路径】 转换为 【服务器的磁盘路径】
// 获取浏览器实际输入路径
String uri = req.getRequestURI();
// 转换为 : 图片的磁盘路径
String path = "G:" + uri;
// 如果文件不存在,返回404
if( !new File(path).exists() ) {
resp.sendError(404);
return ;
}
// 如果存在,从文件输入流读取,向响应的输出流写
FileInputStream is = new FileInputStream(path);
OutputStream os = resp.getOutputStream();
// 利用工具类 将响应的输出流输出
IOUtils.copy(is, os);
IOUtils.closeQuietly(is);
}
}