赞
踩
/** * 用户模块业务层的实现类 */ @Override public void reg(User user) { //通过user参数来获取传递过来的username String username = user.getUsername(); //调用mapper的findByUsername(username)判断用户是否被注册过了 User result = userMapper.findByUsername(username); //判断结果集是否为null,不为null的话则需抛出用户名被占用的异常 if (result != null) { // 抛出异常 throw new UsernameDuplicatedException("'用户名被占用,请换个用户名"); } // 密码加密处理的实现:md5算法的形式:67sssss-67sssss-67sssss-67sssss // 串 + password + 串 ---- md5算法进行加密,连续加载三次 // 盐值 + password + 盐值 ---- 盐值就是一个随机的字符串 String oldPassword = user.getPassword(); // 获取盐值(随机生成字符串) String salt = UUID.randomUUID().toString().toUpperCase(); // 补全数据:盐值的记录 user.setSalt(salt); // 将密码和盐值作为一个整体作为加密处理,忽略原有密码强度提升了数据的安全性 String md5Password = getMD5Password(oldPassword, salt); // 将加密之后的密码重新补全设置到 user 对象中 user.setPassword(md5Password); //补全数据:is_delete设置为0 user.setIsDelete(0); //补全数据:四个日志字段信息 user.setCreatedUser(user.getUsername()); user.setModifiedUser(user.getUsername()); Date date = new Date(); user.setCreatedTime(date); user.setModifiedTime(date); // 执行注册业务层功能的实现 Integer rows = userMapper.insert(user); if (rows != 1) { throw new InsertException("在用户注册过程中产生未知错误"); } } @Override public User login(String username, String password) { //根据用户名称来查询用户的数据是否存在,不存在则抛出异常 User result = userMapper.findByUsername(username); if (result == null) { throw new UserNotFoundException("用户数据不存在"); } /** * 检测用户的密码是否匹配: * 1.先获取数据库中加密之后的密码 * 2.和用户传递过来的密码进行比较 * 2.1先获取盐值,注册时自动生成的盐值 * 2.2将获取的用户密码按照相同的md5算法加密 */ String oldPassword = result.getPassword(); String salt = result.getSalt(); String newMd5Password = getMD5Password(password, salt); if (!newMd5Password.equals(oldPassword)) { throw new PasswordNotMatchException("用户密码错误"); } //判断is_delete字段的值是否为1,为1表示被标记为删除 if (result.getIsDelete() == 1) { throw new UserNotFoundException("用户数据不存在"); } //方法login返回的用户数据是为了辅助其他页面做数据展示使用(只会用到uid,username,avatar) /* 所以可以new一个新的user只赋这三个变量的值,这样使层与层之间传输时数据体量变小,后台层与 层之间传输时数据量越小性能越高,前端也是的,数据量小了前端响应速度就变快了 */ User user = new User(); user.setUid(result.getUid()); user.setUsername(result.getUsername()); user.setAvatar(result.getAvatar()); return user; } /** * 定义一个md5算法的加密处理 */ private String getMD5Password(String password, String salt) { // md5加密算法方法的调用(进行三次加密) for (int i = 0; i < 3; i++) { password = DigestUtils.md5DigestAsHex((salt + password + salt).getBytes()).toUpperCase(); } // 返回加密之后的密码 return password; }
源码:
public interface HandlerInterceptor {
// 在调用所有处理请求的方法之前被自动调用执行的方法
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
// 在 ModelAndView 对象返回之后被调用的方法
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}
// 在整个请求所有关联的资源被执行完毕最后所执行的方法
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
}
}
实现,创建在interceptor包下
/** * 定义一个登录拦截器 */ public class LoginInterceptor implements HandlerInterceptor { /** * 监测全局 session 对象中是否有 uid数据,如果有则放行,如果没有重定向到登录界面 * @param request 请求对象 * @param response 响应对象 * @param handler 处理器(url + Controller: 映射) * @return 如果返回值为true表示放行当前的请求,如果返回值为false则表示拦截当前的请求 * @throws Exception */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // HttpServletRequest 对象来获取session对象 Object obj = request.getSession().getAttribute("uid"); if (obj == null) { // 说明用户没有登录过系统,则重定向到 login.html页面 response.sendRedirect("/web/login.html"); // 结束后续的调用 return false; } // 请求放行 return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { HandlerInterceptor.super.afterCompletion(request, response, handler, ex); } }
创建在 config 包下
/** * 处理器拦截器的注册 */ @Configuration // 加载当前的拦截器并进行注册 public class LoginInterceptorConfigurer implements WebMvcConfigurer { /** * 配置拦截器 * @param registry */ @Override public void addInterceptors(InterceptorRegistry registry) { // 创建自定义拦截器对象 HandlerInterceptor interceptor = new LoginInterceptor(); // 配置白名单:存放在一个 list集合 List<String> patterns = new ArrayList<>(); patterns.add("/bootstrap3/**"); patterns.add("/css/**"); patterns.add("/image/**"); patterns.add("/js/**"); patterns.add("/web/register.html"); patterns.add("/web/login.html"); patterns.add("/web/index.html"); patterns.add("/web/product.html"); patterns.add("/users/reg"); patterns.add("/users/login"); // 完成拦截器的注册 registry.addInterceptor(interceptor) .addPathPatterns("/**") // 表示要拦截的url是什么 .excludePathPatterns(patterns); } }
如果提示重定向次数过多,login.html页面无法打开。将浏览器cookie请求清空,再将浏览器设置为初始设置。
错误方法:把文件存到数据库中,需要图片时访问数据库,数据库将文件解析为字节流返回,最后写到本地的某一个文件.这种方法太耗费资源和时间了
正确方法:将对应的文件保存在操作系统上,然后再把这个文件路径记录下来,因为在记录路径的时候是非常便捷和方便的,将来如果要打开这个文件可以依据这个路径找到这个文件,所以说在数据库中保存该文件的路径即可.
稍微大一点的公司都会将所有的静态资源(图片,文件,其他资源文件)放到某台电脑上,再把这台电脑作为一台单独的服务器使用
对应一个更新用户 avatar 字段的 sql 语句
update t_user set avatar=?,modified_user=?,modified_time=? where uid=?
在UserMapper接口中定义一个抽象方法用于修改用户的头像
/** * 注解@Param("SQL映射文件中#{}占位符的变量名"),解决的问题: * 当SQL语句的占位符和映射的接口方法参数名不一致时,需要将某个参数强行注入到某个 * 占位符变量上时,可以使用@Param这个注解来标注映射的关系 * 根据用户uid修改用户的头像 * @param uid 用户id * @param avatar 头像地址 * @param modifiedUser 修改者 * @param modifiedTime 修改时间 * @return 返回受影响行数 */ Integer updateAvatarByUid(@Param("uid") Integer uid,//@Param("参数名")注解中的参数名需要和sql语句中 //的#{参数名}的参数名保持一致.该处表示iddddd中的变量值要注入到sql语句的uid中 @Param("avatar") String avatar, @Param("modifiedUser") String modifiedUser, @Param("modifiedTime") Date modifiedTime);
<update id="updateAvatarByUid">
UPDATE store.t_user
SET
avatar=#{avatar},
modified_user=#{modifiedUser},
modified_time=#{modifiedTime}
WHERE
uid=#{uid}
</update>
在测试类中编写测试方法:也可使用 Postman
@Test
public void updateAvatarByUid() {
userMapper.updateAvatarByUid(6, "/upload/avatar.png", "管理员", new Date());
}
/**
* 修改用户头像
* @param uid 用户id
* @param avatar 用户头像路径
* @param username 用户名称
*/
void changeAvatar(Integer uid, String avatar, String username);
@Override
public void changeAvatar(Integer uid, String avatar, String username) {
User result = userMapper.findByUid(uid);
if (result == null || result.getIsDelete() == 1) {
throw new UserNotFoundException("用户数据不存在");
}
Integer rows = userMapper.updateAvatarByUid(uid, avatar, username, new Date());
if (rows != 1) {
throw new UpdateException("更新用户头像产生未知异常");
}
}
测试业务层方法
@Test
public void updateAvatarByUid() {
userService.changeAvatar(6, "/upload/test.png", "管理员");
}
文件异常的父类
FileUploadException 泛指文件上传异常(父类) 继承 RuntimeException
FileEmptyException 文件为空的异常
FileStateException 文件上传状态异常
FileSizeException 文件大小超出限制
FIleTypeException 文件类型异常
FileUploadIOException 文件读写异常
五个构造方法显示声明出来,再去继承相关的父类
在基类 BaseController 类中进行编写和统一处理
else if (e instanceof FileEmptyException) { result.setState(6000); result.setMessage("文件上传为空的异常"); } else if (e instanceof FileSizeException) { result.setState(6001); result.setMessage("文件大小超出限制异常"); }else if (e instanceof FileTypeException) { result.setState(6002); result.setMessage("文件类型异常"); } else if (e instanceof FileStateException) { result.setState(6003); result.setMessage("文件上传状态异常"); } else if (e instanceof FileUploadIOException) { result.setState(6004); result.setMessage("文件读写异常"); }
在异常统一处理方法的参数列表上增加新的异常处理作为它的参数
@ExceptionHandler({ServiceException.class, FileUploadException.class}) // 用于统一处理抛出的异常
设计请求
实现
/** * 设置上传文件的最大值 */ public static final int AVATAR_MAX_SIZE = 10 * 1024 * 1024; /** * 限制上传文件的类型 */ public static final List<String> AVATAR_TYPE = new ArrayList<>(); static { AVATAR_TYPE.add("images/jpeg"); AVATAR_TYPE.add("images/png"); AVATAR_TYPE.add("images/bmp"); AVATAR_TYPE.add("images/gif"); } /** * MultipartFile 接口 是 SpringMVC 提供的一个接口,这个接口为我们包装了 * 获取文件类型的数据(任何类型的file都可以接收),SpringBoot 它又整合了 * SpringMVC,只需要在处理请求的方法参数列表声明一个参数类型为 MultipartFile * 的参数,然后 SpringBoot 自动将传递给服务的文件数据赋值给这个参数 * * <input type= * "file" name="file">中的name="file",所以必须有一个方法的参数名 * 为file用于接收前端传递的该文件.如果想要参数名和前端的name不一 * 样:@RequestParam("file") MultipartFile file:把表单中name= * "file"的控件值传递到变量file上 * * @param session * @param file * @return */ @RequestMapping("change_avatar") public JsonResult<String> changeAvatar(HttpSession session, @RequestParam("file") MultipartFile file) { // 判断文件是否为 null if (file.isEmpty()) { throw new FileEmptyException("文件为空"); } // 判断文件大小 if (file.getSize() > AVATAR_MAX_SIZE) { throw new FileSizeException("文件大小超出限制"); } // 判断文件类型是否是我们规定的后缀类型 String contentType = file.getContentType(); // 如果集合包含某个元素则返回 true if (!AVATAR_TYPE.contains(contentType)) { throw new FileTypeException("文件类型不支持"); } // 上传的文件 ../upload/文件名.png String parent = session.getServletContext().getRealPath("upload"); // File 对象 指向这个路径,File 是否存在 File dir = new File(parent); if (!dir.exists()) { // 检测目录是否存在 dir.mkdirs(); // 创建当前目录 } // 获取到这个文件名称, UUID 工具来生成一个新的字符串作为文件名 // 例如: avatar1.png String originalFilename = file.getOriginalFilename(); System.out.println("originalFilename=" + originalFilename); int index = originalFilename.lastIndexOf("."); String suffix = originalFilename.substring(index); String filename = UUID.randomUUID().toString().toUpperCase() + suffix; // 创建文件路径 File dest = new File(dir, filename); // 此时是一个空文件 // 将参数 file 中数据写入到这个空文件中 try { file.transferTo(dest); // 将 参数file文件中的数据传入到 dest文件中 } catch (FileStateException e) { throw new FileStateException("文件状态异常"); } catch (IOException e) { throw new FileUploadIOException("文件读写异常"); } Integer uid = getuidFromSession(session); String username = getUsernameFromSession(session); // 返回头像的路径 /upload/test.png String avatar = "/upload" + filename; // 调用头像修改 userService.changeAvatar(uid, avatar, username); // 返回用户头像路径给前端页面,将来用于头像展示使用 return new JsonResult<>(OK, avatar); }
<form action="/users/change_avatar" method="post" enctype="multipart/form-data">
SpringMVC 默认为 1MB 文件可以进行上传,手动的去修改 SpringMVC 默认上传文件的大小
方式一:直接可以在配置文件中进行配置
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=15MB
方式二:需要采用 Java 代码的形式来设置文件的上传大小的限制。主类中配置,可以定义一个方法,必须使用@Bean来修饰。在类的前面添加 @Configuration注解来进行修改类。 MultipartConfigElement
@Configuration // 表示配置类 @SpringBootApplication @MapperScan("com.cy.store.mapper") public class StoreApplication { public static void main(String[] args) { SpringApplication.run(StoreApplication.class, args); } public MultipartConfigElement getMultipartConfigElement() { // 创建一个配置的工厂类对象 MultipartConfigFactory factory = new MultipartConfigFactory(); // 设置需要创建的对象的相关信息 factory.setMaxFileSize(DataSize.of(10, DataUnit.MEGABYTES)); factory.setMaxRequestSize(DataSize.of(15, DataUnit.MEGABYTES)); // 通过工厂类来创建 MultipartConfigElement 对象 return factory.createMultipartConfig(); } }
在页面中通过ajax请求来提交文件,提交完成后返回了json串,解析出json串中的data数据设置到img标签的src属性上
1.serialize():可以将表单数据自动拼接成key=value的结构提交给服务器,一般提交的是普通的控件类型中的数据(type=text/password/radio/checkbox等等)
2.FormData类:将表单中数据保持原有的结构进行数据提交.文件类型的数据可以使用FormData对象进行存储
使用方法:new FormData($(“form”)[0]);
这行代码的含义是将id="form"的表单的第一个元素的整体值作为创建FormData对象的数据
3.虽然我们把文件的数据保护下来了,但是ajax默认处理数据时按照字符串的形式进行处理,以及默认会采用字符串的形式进行数据提交.手动关闭这两个功能:
processData: false,//处理数据的形式,关闭处理数据
contentType: false,//提交数据的形式,关闭默认提交数据的形式
<!--上传头像表单开始--> <form id="form-change-avatar" class="form-horizontal" role="form"> <div class="form-group"> <label class="col-md-2 control-label">选择头像:</label> <div class="col-md-5"> <img id="img-avatar" src="../images/index/user.jpg" class="img-responsive" /> </div> <div class="clearfix"></div> <div class="col-md-offset-2 col-md-4"> <input type="file" name="file"> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <input id="btn-change-avatar" type="button" class="btn btn-primary" value="上传" /> </div> </div> </form> <script type="text/javascript"> $("#btn-change-avatar").click(function () { $.ajax({ url: "/users/change_avatar", type: "POST", data: new FormData($("#form-change-avatar")[0]), processData: false, // 处理数据的形式,关闭处理数据 contentType: false, // 提交数据的形式,关闭默认提交数据的形式 dataType: "JSON", success: function (json) { if (json.state === 200) { alert("头像修改成功"); // 将服务器端返回的头像地址设置 img 标签的 src 属性上 // attr(属性,属性值)用来给某个属性设值 $("#img-avatar").attr("src", json.data); } else { alert("头像修改失败"); } }, error: function (xhr) { alert("修改头像时产生未知异常" + xhr.message); } }); }); </script>
将头像上传后会显示头像,但是关闭浏览器后再进入个人头像页面就不会显示头像了,因为只有点击"上传"才能发送ajax请求并显示头像.
可以在每次用户登录成功后将avatar保存在cookie中,登录的业务层返回给控制层user对象,该对象包含uid,username,avatar.所以要在登录页面login.html中将服务器返回的头像路径设置到cookie中,然后每次检测到用户打开上传头像页面,在这个页面中通过ready()方法来自动读取cookie中头像路径并设到src属性上
1、设置cookie的值
(1) 需要在login.html页面头部导入cookie.js文件
(2) 调用cookie方法保存路径
$.cookie(key,value,time);//time单位:天
(3) 在ajax请求原有的代码上加$.cookie(“avatar”,json.data.avatar,{expires: 7});
success: function (json) {
if (json.state == 200) {
location.href = "index.html";
$.cookie("avatar",json.data.avatar,{expires: 7});
} else {
alert("登录失败")
}
},
(4) 然后需要在upload.html获取cookie中的值,所以要在页面头部导入cookie.js文件
(5) 在upload.html的script标签中加ready()自动读取cookie数据
$(document).ready(function () {
let avatar = $.cookie("avatar");
// 将cookie值获取出来设置到头像 src属性上
$("#img-avatar").attr("src", avatar);
})
上传头像后不重新登录而是浏览其他页面,然后再进入个人头像页面时展示的头像是上次上传的,因为此时cookie中的值是上次上传的头像的路径,所以需要上传头像后使用同名覆盖更改cookie中路径
在ajax函数的success属性值的if语句加:
$.cookie("avatar",json.data,{expires: 7});
user.address.max-count=20
/**
* 为了方便日后修改最大收货地址数量,可以在配置文件
* application.properties中定义user.address.max-count=20
*/
//spring读取配置文件中数据:@Value("${user.address.max-count}")
@Value("${user.address.max-count}")
private Integer maxCount;
<a href="product.html?id=#{id}">
<!-- 用于截取在 index.html页面中 点击商品,通过 product.html?id=#{id} 来获取 商品的id -->
<script type="text/javascript" src="../js/jquery-getUrlParam.js"></script>
<script type="text/javascript">
//调用jquery-getUrlParam.js文件的getUrlParam方法获取商品id
var id = $.getUrlParam("id");
console.log("id=" + id);
</script>
//html()方法:
// 假设有个标签<div id="a"></div>
//那么$("#a").html(<p></p>)就是给该div标签加p标签
//$("#a").html("我爱中国")就是给该div标签填充"我爱中国"内容
$("#product-title").html(json.data.title);
data: $(“form表单选择”).serialize()。当参数过多并且在同一个表单中,字符串的提交等
data: new FormData($(“form表单选择”)[0])。只适合提交文件
data: “username=Tome”。适合参数值固定并且参数列表有限,可以进行手动拼接
let user = "TOM"
data: "username=" + user
适合 JSON 格式提交数据
data: {
"username": "Tome",
"age": 18,
"sex": 0
}
VO全称Value Object,值对象。当进行select查询时,查询的结果属于多张表中的内容,此时发现结果集不能直接使用某个POJO实体类来接收,因为POJO实体类不能包含多表查询出来的信息,解决方式是:重新去构建一个新的对象,这个对象用于存储所查询出来的结果集对应的映射,所以把这个对象称之为值对象.
public class CartVO implements Serializable {
private Integer cid;
private Integer uid;
private Integer pid;
private Long price;
private Integer num;
private String title;
private Long realPrice;
private String image;
}
data: location.search.substr(1)这个API的参数为1表示截取地址栏中?后面的数据,即参数
如果这个API的参数为0则表示截取地址栏中?前面的数据,即请求地址
检测项目所有业务层方法的耗时(开始执行时间和结束执行时间只差值),再在不改变项目主体流程代码的前提条件下完成此功能,就要用到AOP
如果我们想对业务某一些方法同时添加相同的功能需求,并且在不改变业务功能逻辑的基础之上进行完成,就可以使用AOP的切面编程进行开发
AOP:面向切面(Aspect)编程。AOP并不是Spring框架的特性(Spring已经被整合到了SpringBoot中,所以如果AOP是Spring框架的特性,那么就不需要手动导包,只需要在一个类上写@Aspect注解,鼠标放到该注解上按alt+enter就可以自动导包了,但是事与愿违,所以说AOP并不是Spring框架的特性),只是Spring很好的支持了AOP。
1.切面方法的访问权限是public。
2.切面方法的返回值类型可以是void或Object,如果该方法被@Around注解修饰,必须使用Object作为返回值类型,并返回连接点方法的返回值;如果使用的注解是@Before或@After等其他注解时,则自行决定。
3.切面方法的名称可以自定义。
4.切面方法可以接收参数,参数是ProccedingJoinPoint接口类型的参数.但是@Around所修饰的方法必须要传递这个参数.其他注解修饰的方法要不要该参数都可以
1.因为AOP不是Spring内部封装的技术,所以需要进行导包操作:在pom.xml文件中添加两个关于AOP的依赖aspectjweaver和aspectjtools。
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
</dependency>
2.在com.cy.store.aop包下创建TimerAspect切面类,给类添加两个注解进行修饰:
@Aspect
@Component
public class TimerAspect {
}
3.在类中添加切面方法,这里使用环绕通知的方式来进行编写。ProceedingJoinPoint 接口表示连接点,目标方法的对象
public Object around(ProceedingJoinPoint pjp) throws Throwable {
//开始时间
long start = System.currentTimeMillis();
//调用目标方法,比如login方法,getByUid方法
Object result = pjp.proceed();
//结束时间
long end = System.currentTimeMillis();
System.out.println("耗时:"+(end-start));
return result;
}
4.将当前环绕通知映射到某个切面上,也就是指定连接的点.给around方法添加注解@Around
@Around("execution(* com.cy.store.service.impl.*.*(..))")
5.启动项目,在前端浏览器访问任意一个功能模块进行功能的测试
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。