Spring项目准备
十四.项目前奏
14.1整合加上传下载
14.1.1 修改之前的上传代码
package com.aaa.controller;
import com.aaa.pojo.User;
import com.aaa.service.UserService;
import com.aaa.service.UserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
/**
* Created by 张晨光 on 2020/7/31 15:19
* 控制器Controller,作用和之前的Servlet一样;
*/
@Controller
public class FileController {
//仍然使用依赖注入,来调用业务层对象的;
//证明这个时候注入不成功;
@Resource(name = "userService")
UserService userService;
@RequestMapping("/upload")
public String upload(User user,MultipartFile fileImg,HttpServletRequest request) throws IOException {
//d://aaa/idea/.../upload
String path=request.getServletContext().getRealPath("/upload");
String fileName=fileImg.getOriginalFilename();
String ext=fileName.substring(fileName.lastIndexOf("."));
fileName=UUID.randomUUID().toString()+ext;
//上传的时候用到什么????
//IO流:InputStream/OutputStream; Reader/Writer
//第二个参数有文件名;
File file=new File(path,fileName);
//如果file不存在,或者
if(!file.exists()||!file.isDirectory())
{
file.mkdirs();//创建目录和子目录
}
//这时候,已经确定了目录;
fileImg.transferTo(file);
//设置用户的图片,是fileName
user.setImg(fileName);
userService.saveUser(user);
//完毕之后去了一个hello页面,要查询所有用户信息
//return "hello";
return "forward:user/getUsers";
}
}
14.1.2 UserController
更改如下,之前的无用代码都删除掉
@Controller
@RequestMapping("/user")
public class UserController {
//调用业务层对象;\
@Autowired
UserService userService;
@RequestMapping("/getUsers")
public String getUsers(Model model){
//业务层对象,查询用户信息
List<User> userList = userService.selectAllUsers();
model.addAttribute("userList",userList);
//将来要去View-》JSP页面
return "userlist";
}
}
14.1.3 Model层代码
UserDao
public interface UserDao {
//增加用户信息
void saveUser(User user);
//用户编辑
void updateUser(User user);
void delUser(int id);//根据id来删除用户
User findUserById(int id);//根据id来查询用户操作;
//要再次做下查询所有用户信息
List<User> selectAllUsers();
}
UserDaoImpl,注意写增加代码和查询所有代码即可,如果有了,不用动
@Repository("userDao")
public class UserDaoImpl implements UserDao {
//使用自动注入,来调用Spring容器提供的JdbcTemplate这个Bean
@Resource(name = "jdbcTemplate")
JdbcTemplate jdbcTemplate;
public void saveUser(User user) {
String sql="insert user(name,img)values(?,?)";
Object[]params={user.getName(),user.getImg()};
System.out.println("正式用户");
jdbcTemplate.update(sql,params);
}
public void updateUser(User user) {
String sql="update user set pwd=? where id=?"; //有?了这么办?
int result=jdbcTemplate.update(sql,user.getPwd(),user.getId());//Object...params
if(result>0)
System.out.println("更新成功");
else
System.out.println("更新失败!");
}
public void delUser(int id) {
System.out.println("模拟根据id来实现删除用户");
}
public User findUserById(int id) {
System.out.println("根据id来查询单一用户信息");
return null;
}
//dao层实现类的获取;查询的是单表数据 ;
public List<User> selectAllUsers() {
String sql="select * from user ";
//String sql="select * from user where gender=? and ";
//query查询方法的参数:sql
//固定的格式:BeanPropertyRowMapper<待查询的类名>(待查询类.class) 适用于单一类;
//原理:就是通过User.class这个字节码文件,利用反射机制,来读取里面的元数据信息
//获取查询列表的,使用List<User>
List<User> userList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<User>(User.class), null);
//List<User> userList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<User>(User.class), "男");
return userList;
}
}
UserService
public interface UserService {
//增加用户信息,业务
void saveUser(User user);
void updateUser(User user);
void delUser(int id);//根据id来删除用户
User findUserById(int id);//根据id来查询用户操作;
//查询所有用户信息
List<User> selectAllUsers();
}
UserServiceImpl,只写增加和查询的,其他不用管
@Service("userService")
public class UserServiceImpl implements UserService{
//业务层实现类,调用dao对象;
//UserDao userDao=new UserDaoImpl();利用Spring容器来解耦,它来直接托管Bean的创建
@Resource(name="userDao") //必须有name=
UserDao userDao;
//下面这些代码和之前javaweb分层的代码一模一样
public void saveUser(User user) {
userDao.saveUser(user);
}
public void updateUser(User user) {
userDao.updateUser(user);
}
public void delUser(int id) {
userDao.delUser(id);
}
public User findUserById(int id) {
return userDao.findUserById(id);
} //
public List<User> selectAllUsers() {
return userDao.selectAllUsers();
}
}
userlist.jsp代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@page isELIgnored="false" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Title</title>
<style>
table,td{
border: 1px solid #f00;border-collapse: collapse;
text-align: center;
}
img{width:80px;height: 60px;}
</style>
</head>
<body>
Userlist列表信息展示<br/>
<table>
<tr>
<td>编号</td>
<td>姓名</td>
<td>图片</td>
<td>操作</td>
</tr>
<%--tr嵌套到forEach 里面--%>
<c:forEach items="${userList}" var="user">
<tr>
<td>${user.id}</td>
<td> ${user.name}</td>
<td><img src="/upload/${user.img}"/></td>
<td>编辑|删除</td>
</tr>
</c:forEach>
</table>
</body>
</html>
效果:
13.1.4 下载
springmvc提供的ResponseEntity类型,使用它可以很方便地定义返回的HttpHeaders和HttpStatus
在userList.jsp下增加下载的文件
<a rel="nofollow" href="/download?fileName=${user.img}">下载</a>
方式一:在FileController下增加代码
@RequestMapping("/download")
public ResponseEntity<byte[]> export(String fileName,String filePath,HttpServletRequest request) throws IOException {
HttpHeaders headers = new HttpHeaders();
filePath=request.getServletContext().getRealPath("/upload");
File file = new File(filePath+"/"+fileName);
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentDispositionFormData("attachment", fileName);
return new ResponseEntity<byte[]>(FileUtils.readFileToByteArray(file),headers, HttpStatus.CREATED);
}
方式二:稍微复杂一点的,这个代码多,但是更好理解。
知识点:response,响应对象;
ServletOutpuStream 输出字节流
@RequestMapping("/download2")
public String download( String fileName ,String filePath, HttpServletRequest request, HttpServletResponse response)
{
try {
//获取页面输出流
ServletOutputStream outputStream = response.getOutputStream();
filePath=request.getServletContext().getRealPath("/upload");
File file = new File(filePath+"/"+fileName);
byte[] bytes = FileUtils.readFileToByteArray(file); //把文件以二进制形式写回
//向输出流写文件
//写之前设置响应流以附件的形式打开返回值,这样可以保证前边打开文件出错时异常可以返回给前台
response.setHeader("Content-Disposition","attachment;filename="+fileName);
outputStream.write(bytes);
outputStream.flush();
outputStream.close();
return "success";
} catch (IOException e) {
e.printStackTrace();
return "error";
}
}
下载效果如下:
14.2 整合加日期类型
数据
该问题一般是:
1.实体类属性和数据库、页面不一致引起;
2.前端提交的内容在后端一般都用String类型来接收,用Date类型接收会报错。
3.controller代码方法中使用了@RequestParam注解,但是在jsp中没有对应的@RequestParam注解name属性参数值,而且@RequestParam注解的required属性默认为true,也就是说,jsp中参数值必须对应@RequestParam注解的name属性值。
解决日期类型的几种方法
注意:2020/01/01,这种方式的时候,不会报错;报错的格式是2020-01-01,-,.包括年月日 ,都会出错;
14.2.1 实体类修改
在日期变量处直接添加@DateTimeFormat(pattern="yyyy-MM-dd")
14.2.2 Controller修改
@InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:MM:ss");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
//true:允许输入空值,false:不能为空值
}
如果这里面,日期格式:课程的图片上传,LessonController,user图标上传,UserController,部门创建日期,deptController,
Score,class,Grade,很多个Controller都涉及到日期,我们是不是需要在这么多的。Controller都加上上面的这段代码。
14.2.3自定义类型转换器
我们在使用SpringMVC时,常常需要把表单中的参数映射到我们对象的属性中,我们可以在默认的spring-servlet.xml加上如下的配置即可做到普通数据类型的转换,如将String转换成Integer和Double等:对于日期来说,Spring支持的格式是2019/11/11,当我们传入2019-11-11,程序会报错,这时候就需要我们自定义类型转换器来满足我们的需要。
使用详细步骤:
1.Convert接口
public interface Converter<S, T> {
//S:来源,String类型
//T:目标,Target
@Nullable
T convert(S var1);
}
2.自定义类型实现类
public class DateConvert implements Converter<String, Date> {
@Override
public Date convert(String value) {
SimpleDateFormat sdf=null;
//参考一下即可;
if(value==null)
throw new RuntimeException("日期不能为空");
else if(value.contains("年"))
sdf =new SimpleDateFormat("yyyy年mm月dd日");
else if(value.contains("-"))
sdf=new SimpleDateFormat("yyyy-mm-dd");
else if(value.contains("."))
sdf=new SimpleDateFormat("yyyy.mm.dd");
//将日期转换一下;
try {
return sdf.parse(value);
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
}
3.配置springmvc
<!--3.配置自定义类型转换器-->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<!--增加自定义的类型转换器 -->
<bean class="com.aaa.utils.DateConvert"/>
</set>
</property>
</bean>
<mvc:annotation-driven conversion-service="conversionService"/>
dao层对象,sql代码更改
public void saveUser(User user) {
//把之前没有的日期sql代码增加过来过去.
String sql="insert user(name,img,birth)values(?,?,?)";
Object[]params={user.getName(),user.getImg(),user.getBirth()};
jdbcTemplate.update(sql,params);
}
第三种的好处,是适用于全局项目,大家练习的时候,直接第三种。
14.3异常
简单异常信息处理:
14.3.1 假设Controller信息报错如何办?500如何办?
@Controller
public class ExpController {
@RequestMapping("/exp")
public String exp(){
//异常:
System.out.println(10/0); //除0错;
return "hello";
}
}
14.3.2 在SpringMVC.xml下增加如下配置
最简单的配置.复制粘贴.
<!--自定义异常视图处理器-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<!--默认页面处理代码-->
<property name="defaultErrorView" value="error"/>
<!-- 定义异常处理页面用来获取异常信息的变量名,默认名为exception -->
<property name="exceptionAttribute" value="ex"></property>
</bean>
14.3.3
错误信息页面处理
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%--可以使用el表达式,否则el表达式起不到作用;--%>
<html>
<head>
<title>Title</title>
</head>
<body>
error
<img src="/static/img/500.png"/>
<%--输出刚才的那个异常的属性,ex;--%>
<c:out value="${ex}"></c:out>
</body>
</html>
总结:
1.整合上传下载
2.日期增加
3.统一异常类型处理
扩展实现
<bean id="exceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="java.lang.Exception">errors/error</prop>
<prop key="java.lang.Throwable">errors/err</prop>
</props>
</property>
<property name="statusCodes">
<props>
<prop key="errors/error">500</prop>
<prop key="404">404</prop>
</props>
</property>
<!-- 设置日志输出级别,不定义则默认不输出警告等错误日志信息 -->
<property name="warnLogCategory" value="WARN"></property>
<!-- 默认错误页面,当找不到上面mappings中指定的异常对应视图时,使用本默认配置 -->
<property name="defaultErrorView" value="errors/error"></property>
<!-- 默认HTTP状态码 -->
<property name="defaultStatusCode" value="500"></property>
</bean>
十五.项目分页
分页是项目中非常重要的一个功能实现,实现方式有多种。
这是一个分页导航,其中能够得到的数据有
pageNo: 当前页数
pageSize: 一页显示多少条记录
countPages: 一共多少页
countRows: 一共多少条
//mysql-->分页
pageList: 数据集合,List类型
业务流程图:
分页步骤如下:
1.定义pageBean类
作为存储分页数据的Bean
package com.aaa.utils;
import java.util.List;
/**
* Created by 张晨光 on 2020/8/6 21:14
*/
public class PageBean {
private int pageNo; //当前页码
private int pageSize=3; //一页显示多少条记录
private int countPages; //总共多少页
private int countRows; //公共多少条记录
private List pageList; //查询的记录集合
public int getPageNo() {
return pageNo;
}
public void setPageNo(int pageNo) {
this.pageNo = pageNo;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public int getCountPages() {
return countPages;
}
public void setCountPages(int countPages) {
this.countPages = countPages;
}
public int getCountRows() {
return countRows;
}
public void setCountRows(int countRows) {
this.countRows = countRows;
}
public List getPageList() {
return pageList;
}
public void setPageList(List pageList) {
this.pageList = pageList;
}
}
2.pageUtil类,通用查询分页类
代替了之前在很多个Servlet里面,写的一堆代码,BookServlet,ProducetServlet,OrderServlet,
提取公共的代码,封装一下。
作用: 获取分页数据
1.作用查询总条数
2.获取总页数
3.查询分页后数据集合
问题1:拼接字符串
问题2:mysql分页如何写
package com.aaa.utils;
import com.aaa.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
/**
* Created by 张晨光 on 2020/8/6 21:43
*/
@Component
public class PageUtils {
@Resource(name = "jdbcTemplate")
JdbcTemplate jdbcTemplate;
public void selectPage(String sql,PageBean page){
//1.查询总条数
StringBuffer stringBuffer=new StringBuffer("select count(*) countRows from( ");
stringBuffer.append(sql);
stringBuffer.append(") a");
//获取数据库中的总条数;
//注意:这个使用的是查询单列的聚合数据
Integer countRows = jdbcTemplate.queryForObject(sb1.toString(),Integer.class);;
//1.2.将总条数放入pageBean中
page.setCountRows(countRows);
//2.计算总页数;
int countPages=0;
countPages=countRows%page.getPageSize()==0?(countRows/page.getPageSize()):(countRows/page.getPageSize()+1);
//将总页数存入pageBean
page.setCountPages(countPages);
//3.查询分页的集合;
StringBuffer sb=new StringBuffer(sql);
sb.append(" limit ");
sb.append((page.getPageNo()-1)*page.getPageSize());
sb.append(",");
sb.append(page.getPageSize());
//这个在输入查询的sb.toString(),切记别写错;
List<User> userList = jdbcTemplate.query(sb.toString(), new BeanPropertyRowMapper<User>(User.class));
page.setPageList(userList);
}
}
3.dao层
3.1 UserDao接口
//查询用户数量数量;
void selectAllUsers(PageBean page);
3.2UserDaoImpl实现类
注意,需要在这里使用pageUtil的注解实现
@Override
public void selectAllUsers(PageBean page) {
String sql="select * from user";
pageUtils.selectPage(sql,page);
}
4.Service层
4.1UserService接口
//查询用户数量数量;
void selectAllUsers(PageBean page);
4.2UserServiceImpl实现类
@Override
public void selectAllUsers(PageBean page) {
userDao.selectAllUsers(page);
}
5.Controller类
控制层的分页:
package com.aaa.controller;
import com.aaa.pojo.User;
import com.aaa.service.UserService;
import com.aaa.utils.PageBean;
import com.mysql.cj.xdevapi.SchemaImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* Created by 张晨光 on 2020/7/27 14:25
* 类前也可以加@RequestMapping:请求映射注解,窄化处理,实际上就是对控制器可以分类;
*/
@Controller
@RequestMapping("/user")
public class UserController {
//调用业务层对象;\
@Autowired
UserService userService;
//第二种方式,增加一个WebDataBinder;
//这个注解的作用,增加一个初始化的数据绑定功能; 只要来调用Controller,就初始化绑定
//这个还是有一定的问题;
/*@InitBinder
public void initBinder(WebDataBinder webDataBinder){
//这个是指定按照我们的日期 时间格式;
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd hh:MM:ss");
sdf.setLenient(false);
//WebDataBinder:页面数据绑定类,注册一个自定义的绑定功能;
//将Date.class这个日期类,和刚才的SimpleDateFormat的对象做下绑定
webDataBinder.registerCustomEditor(Date.class,new CustomDateEditor(sdf,true));
}*/
@RequestMapping("/getUsers")
public String getUsers(Model model){
//业务层对象,查询用户信息
List<User> userList = userService.selectAllUsers();
model.addAttribute("userList",userList);
//将来要去View-》JSP页面
return "userlist";
}
@RequestMapping("/regUser")
public String regUser(User user, MultipartFile fileImg, HttpServletRequest request){
//之前的代码省略
return "";
}
@RequestMapping("/toUserList")
public String toUserList(HttpServletRequest request,HttpServletResponse response){
int pageNo=1;
//接受从前台传递过来的数据,如果不为空,则传递过去;否则是第一页;
if(request.getParameter("pageNo")!=null){
pageNo=Integer.parseInt(request.getParameter("pageNo"));
}
//初始化,分页Bean
PageBean page=new PageBean();
page.setPageNo(pageNo);
//查询数据库,并将查到的数据,提取到PageBean里面
userService.selectAllUsers(page);
//传递值;
request.setAttribute("page",page);
// System.out.println(page.getPageList());
return "forward:getUsers";
}
}
6.View层页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@page isELIgnored="false" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Title</title>
<link rel="stylesheet" href="/static/css/login.css"/>
<script src="/static/js/jquery-2.1.0.min.js"></script>
<script>
function goPage(pageNo) {
location.href='toUserList?pageNo='+pageNo;
}
</script>
</head>
<body>
Userlist列表信息展示<br/>
<table>
<tr>
<td>编号</td>
<td>姓名</td>
<td>图片</td>
<td>操作</td>
</tr>
<%--tr嵌套到forEach 里面--%>
<c:forEach items="${page.pageList}" var="user">
<tr>
<td>${user.id}</td>
<td> ${user.name}</td>
<td><img src="/upload/${user.img}"/>
<%--<a rel="nofollow" href="${pageContext.request.contextPath}/upload/${user.img}">下载</a>--%>
<%--/:下载的图片链接--%>
<a rel="nofollow" href="/download?fileName=${user.img}">下载</a>
</td>
<td>编辑|删除</td>
</tr>
</c:forEach>
<tr>
<td colspan="4">
<a rel="nofollow" href="#" onclick="goPage(1)">首页</a>
<c:if test="${page.pageNo<1}">
<a rel="nofollow" href="#">上一页</a>
</c:if>
<c:if test="${page.pageNo>1}">
<a rel="nofollow" href="#" onclick="goPage(${page.pageNo-1})">上一页</a>
</c:if>
<c:if test="${page.pageNo<page.countPages}">
<a rel="nofollow" href="#" onclick="goPage(${page.pageNo+1})">下一页</a>
</c:if>
<a rel="nofollow" href="#" onclick="goPage(${page.countPages})">尾页</a>
当前第${page.pageNo}页/共${page.countPages}页
</td>
</tr>
</table>
</body>
</html>
7.效果
十六.权限思路
RBAC 是基于角色的访问控制(Role-Based Access Control
)在 RBAC 中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便。
授权实际上是Who
、What
、How
三元组之间的关系,也就是Who
对What
进行How
的操作,也就是“主体”对“客体”的操作。
Who:是权限的拥有者或主体(如:User,Role)。
What:是操作或对象(operation,object)。
How:具体的权限(Privilege,正向授权与负向授权)。
一、权限系统
这一天将讲述一个基本的基于数据库的权限管理系统的设计,在这一天的课程的最后将讲述“左右值无限分类实现算法”如何来优化“系统菜单”的结构而告终。今天的内容和前几天的基础框架是一样的它们都属于基础知识,在这些基础知识上还可以扩展出无数的变种与进化设计。
二、先来看客户的一个需求
2.1 用户实际需求
背景需求:
需要在“权限”=>“角色”=>“用户”
之间,在赋予一个特殊的角色“客服”,这个需求比较常见,我一个用户想把我的权限分配到“客服”角色上,然后由几个“客服”去操作对应的业务流程。比如我们的天猫,淘宝商家后天就是如此,当店铺开到一定的规模,那么就会有分工。
A客服:负责打单填写发货单。
B~E客服:负责每天对我们说“亲,您好。祝亲生活愉快!”,也就是和我们沟通交流的客服。
F~H:负责售后。
... ...
那么这些客服也是归属到这个商家下面去。而且每个商家可能都有类似的客服,分工完全靠商家自己去分配管理。