宁波网站建设模板制作,wordpress文章图片默认居中,网站建设需要矢量文件,高明建网站服务计算机毕业设计|基于SpringBootMyBatis框架的电脑商城的设计与实现#xff08;系统概述与环境搭建#xff09;
该项目分析着重于设计和实现基于SpringBootMyBatis框架的电脑商城。首先#xff0c;通过深入分析项目所需数据#xff0c;包括用户、商品、商品类别、收藏、订单…计算机毕业设计|基于SpringBootMyBatis框架的电脑商城的设计与实现系统概述与环境搭建
该项目分析着重于设计和实现基于SpringBootMyBatis框架的电脑商城。首先通过深入分析项目所需数据包括用户、商品、商品类别、收藏、订单、购物车、收货地址建立了数据模型。在数据开发的原则下提出了一种逻辑的开发流程以用户-收货地址-商品类别-商品-收藏-购物车-订单为顺序确保基础、简单或熟悉的数据先得到处理。 针对用户数据列出了相关功能清单包括登录、注册、修改密码、修改资料、上传头像。为确保系统的有序开发规定了功能的开发顺序遵循增查删改的逻辑。
这篇博客文章为读者提供了清晰的项目分析框架。关于SpringBootMyBatis框架的电脑商城的设计与实现我会按照下面几个部分分章节依次更新到最终完整内容本节是系统概述与环境搭建和用户注册登录部分。 系统概述与环境搭建
1 系统开发及运行环境
电脑商城系统开发所需的环境及相关软件进行介绍。
1.操作系统Windows 10
2.Java开发包JDK 8
3.项目管理工具Maven 3.6.3
4.项目开发工具IntelliJ IDEA 2021.3.2 x64
5.数据库mysql
6.浏览器Google Chrome
7.服务器架构Spring Boot 2.4.7 MyBatis 2.1.4 AJAX
2 项目分析
1.在开发某个项目之前应先分析这个项目中可能涉及哪些种类的数据。本项目中涉及的数据用户、商品、商品类别、收藏、订单、购物车、收货地址。
2.关于数据还应该要确定这些数据的开发顺序。设计开发顺序的原则是先开发基础、简单或熟悉的数据。以上需要处理的数据的开发流程是用户-收货地址-商品类别-商品-收藏-购物车-订单。
3.在开发每种数据的相关功能时先分析该数据涉及哪些功能。在本项目中以用户数据为例需要开发的功能有登录、注册、修改密码、修改资料、上传头像。
4.然后在确定这些功能的开发顺序。原则上应先做基础功能并遵循增查删改的顺序来开发。则用户相关功能的开发顺序应该是注册-登录-修改密码-修改个人资料-上传头像。
5.在实际开发中应先创建该项目的数据库当每次处理一种新的数据时应先创建该数据在数据库中的数据表然后在项目中创建该数据表对应的实体类。
6.在开发某个具体的功能时应遵循开发顺序持久层-业务层-控制器-前端页面。
3 创建数据库
1.首先确保计算机上安装了Mysql数据库将来在数据库中创建与项目相关的表。
2.创建电脑商城项目对应的后台数据库系统store。
CREATE DATABASE store character SET utf8;4 创建Spring Initializr项目
本质上Spring Initializr是一个Web应用程序它提供了一个基本的项目结构能够帮助开发者快速构建一个基础的Spring Boot项目。在创建Spring Initializr类型的项目时需在计算机连网的状态下进行创建。
1.首先确保计算机上安装了JDK、IDEA、Mysql等开发需要使用的软件并在IDEA中配置了Maven 3.6.3项目管理工具。
2.在IDEA欢迎界面点击【New Project】按钮创建项目左侧选择【Spring Initializr】选项进行Spring Boot项目快速构建。 3.将Group设置为com.cyArtifact设置为store其余选项使用默认值。单击【Next】进入Spring Boot场景依赖选择界面。 4.给项目添加Spring Web、MyBatis Framework、MySQL Driver的依赖。点击【Next】按钮完成项目创建。 5.首次创建完Spring Initializr项目时解析项目依赖需消耗一定时间Resolving dependencies of store…。 5 配置并运行项目
5.1 运行项目
找到项目的入口类被SpringBootApplication注解修饰然后运行启动类启动过程如果控制台输出Spring图形则表示启动成功。
package com.cy.store;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;SpringBootApplication
public class StoreApplication {public static void main(String[] args) {SpringApplication.run(StoreApplication.class, args);}
}5.2 配置项目
1.如果启动项目时提示“配置数据源失败:url’属性未指定无法配置内嵌的数据源”。有如下的错误提示。
Failed to configure a DataSource: url attribute is not specified and no embedded datasource could be configured.2.解决以上操作提示的方法在resources文件夹下的application.properties文件中添加数据源的配置。
spring.datasource.urljdbc:mysql://localhost:3306/store?useUnicodetruecharacterEncodingutf-8serverTimezoneAsia/Shanghai
spring.datasource.usernameroot
spring.datasource.password1234563.为了便于查询JSON数据隐藏没有值的属性减少流量的消耗服务器不应该向客户端响应为NULL的属性。可以在属性或类之前添加JsonInclude(valueInclude.NON_NULL)也可以在application.properties中添加全局的配置。
# 服务器向客户端不响应为null的属性
spring.jackson.default-property-inclusionNON_NULL4.SpringBoot项目的默认访问名称是“/”如果需要修改可以手动在配置文件中指定SpringBoot 2.x访问项目路径的项目名。不建议修改。
server.servlet.context-path/store问名称是“/”如果需要修改可以手动在配置文件中指定SpringBoot 2.x访问项目路径的项目名。不建议修改。
server.servlet.context-path/store5.重新启动项目则不在提示配置数据源失败的问题。
用户注册
1 用户-创建数据表
1.使用use命令先选中store数据库。
USE store;2.在store数据库中创建t_user用户数据表。
CREATE TABLE t_user (uid INT AUTO_INCREMENT COMMENT 用户id,username VARCHAR(20) NOT NULL UNIQUE COMMENT 用户名,password CHAR(32) NOT NULL COMMENT 密码,salt CHAR(36) COMMENT 盐值,phone VARCHAR(20) COMMENT 电话号码,email VARCHAR(30) COMMENT 电子邮箱,gender INT COMMENT 性别:0-女1-男,avatar VARCHAR(50) COMMENT 头像,is_delete INT COMMENT 是否删除0-未删除1-已删除,created_user VARCHAR(20) COMMENT 日志-创建人,created_time DATETIME COMMENT 日志-创建时间,modified_user VARCHAR(20) COMMENT 日志-最后修改执行人,modified_time DATETIME COMMENT 日志-最后修改时间,PRIMARY KEY (uid)
) ENGINEInnoDB DEFAULT CHARSETutf8;2 用户-创建实体类
1.项目中许多实体类都会有日志相关的四个属性所以在创建实体类之前应先创建这些实体类的基类将4个日志属性声明在基类中。在com.cy.store.entity包下创建BaseEntity类作为实体类的基类。
package com.cy.store.entity;
import java.io.Serializable;
import java.util.Date;/** 实体类的基类 */
public class BaseEntity implements Serializable {private String createdUser;private Date createdTime;private String modifiedUser;private Date modifiedTime;// Generate: Getter and Setter、toString()
} 因为这个基类的作用就是用于被其它实体类继承的所以应声明为抽象类。 2.创建com.cy.store.entity.User用户数据的实体类继承自BaseEntity类在类中声明与数据表中对应的属性。
package com.cy.store.entity;
import java.io.Serializable;
import java.util.Objects;/** 用户数据的实体类 */
public class User extends BaseEntity implements Serializable {private Integer uid;private String username;private String password;private String salt;private String phone;private String email;private Integer gender;private String avatar;private Integer isDelete;// Generate: Getter and Setter、Generate hashCode() and equals()、toString()
}3 用户-注册-持久层
3.1 准备工作
1.在src/test/java下的com.cy.store.StoreApplicationTests测试类中编写并执行“获取数据库连接”的单元测试以检查数据库连接的配置是否正确。
Autowired
private DataSource dataSource;Test
public void getConnection() throws Exception {System.out.println(dataSource.getConnection());
}2.执行src/test/java下的com.cy.toreApplicationTests测试类中的contextLoads()测试方法以检查测试环境是否正常。
3.2 规划需要执行的SQL语句
1.用户注册的本质是向用户表中插入数据需要执行的SQL语句大致是
INSERT INTO t_user (除了uid以外的字段列表) VALUES (匹配的值列表)2.由于数据表中用户名字段被设计为UNIQUE在执行插入数据之前还应该检查该用户名是否已经被注册因此需要有“根据用户名查询用户数据”的功能。需要执行的SQL语句大致是
SELECT * FROM t_user WHERE username?3.3 接口与抽象方法
1.创建com.cy.store.mapper.UserMapper接口并在接口中添加抽象方法。
package com.cy.mapper;
import com.cy.store.entity.User;/** 处理用户数据操作的持久层接口 */
public interface UserMapper {/*** 插入用户数据* param user 用户数据* return 受影响的行数*/Integer insert(User user);/*** 根据用户名查询用户数据* param username 用户名* return 匹配的用户数据如果没有匹配的数据则返回null*/User findByUsername(String username);
}2.由于这是项目中第一次创建持久层接口还应在StoreApplication启动类之前添加MapperScan(“com.cy.store.mapper”)注解以配置接口文件的位置。 MyBatis与Spring整合后需要实现实体和数据表的映射关系。实现实体和数据表的映射关系可以在Mapper接口上添加Mapper注解。但建议以后直接在SpringBoot启动类中加MapperScan(“mapper包”) 注解这样会比较方便不需要对每个Mapper都添加Mapper注解。 SpringBootApplication
MapperScan(com.cy.store.mapper)
public class StoreApplication {public static void main(String[] args) {SpringApplication.run(StoreApplication.class, args);}
}3.4 配置SQL映射
1.在src/main/resources下创建mapper文件夹并在该文件夹下创建UserMapper.xml映射文件进行以上两个抽象方法的映射配置。
?xml version1.0 encodingUTF-8 ?
!DOCTYPE mapperPUBLIC -//mybatis.org//DTD Mapper 3.0//ENhttp://mybatis.org/dtd/mybatis-3-mapper.dtd
mapper namespacecom.cy.store.mapperresultMap idUserEntityMap typecom.cy.store.entity.Userid columnuid propertyuid/result columnis_delete propertyisDelete/result columncreated_user propertycreatedUser/result columncreated_time propertycreatedTime/result columnmodified_user propertymodifiedUser/result columnmodified_time propertymodifiedTime//resultMap!-- 插入用户数据Integer insert(User user) --insert idinsert useGeneratedKeystrue keyPropertyuidINSERT INTOt_user (username, password, salt, phone, email, gender, avatar, is_delete, created_user, created_time, modified_user, modified_time)VALUES(#{username}, #{password}, #{salt}, #{phone}, #{email}, #{gender}, #{avatar}, #{isDelete}, #{createdUser}, #{createdTime}, #{modifiedUser}, #{modifiedTime})/insert!-- 根据用户名查询用户数据User findByUsername(String username) --select idfindByUsername resultMapUserEntityMapSELECT*FROMt_userWHEREusername #{username}/select
/mapper2.由于这是项目中第一次使用SQL映射所以需要在application.properties中添加mybatis.mapper-locations属性的配置以指定XML文件的位置。
mybatis.mapper-locationsclasspath:mapper/*.xml3.完成后及时执行单元测试检查以上开发的功能是否可正确运行。在src/test/java下创建com.cy.store.mapper.UserMapperTests单元测试类在测试类的声明之前添加RunWith(SpringRunner.class)和SpringBootTest注解并在测试类中声明持久层对象通过自动装配来注入值。 RunWith(SpringRunner.class)注解是一个测试启动器可以加载SpringBoot测试注解。 package com.cy.store.mapper;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;// RunWith(SpringRunner.class)注解是一个测试启动器可以加载Springboot测试注解
RunWith(SpringRunner.class)
SpringBootTest
public class UserMapperTests {Autowiredprivate UserMapper userMapper;}4.如果在第四步自动装配userMapper时报“Could not autowire. No beans of ‘UserMapper’ type found”错无法进行自动装配。解决方案是将Autowiring for bean class选项下的Severity设置为Warning即可。 5.然后编写两个测试方法对以上完成的两个功能进行单元测试。 单元测试方法必须为public修饰方法的返回值类型必须为void方法不能有参数列表并且方法被Test注解修饰。 Test
public void insert() {User user new User();user.setUsername(user01);user.setPassword(123456);Integer rows userMapper.insert(user);System.out.println(rows rows);
}Test
public void findByUsername() {String username user01;User result userMapper.findByUsername(username);System.out.println(result);
}如果出现org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)异常可能原因 1.在resources文件加下创建的mapper文件夹类型没有正确选择eclipse选择Folderidea选择Directory。 2.映射文件的mapper标签的namespace属性没有正确映射到dao层接口或者application.properties中的属性mybatis.mapper-locations没有正确配置xml映射文件。 4 用户-注册-业务层
4.1 业务的定位
1.业务一套完整的数据处理过程通常表现为用户认为的一个功能但是在开发时对应多项数据操作。在项目中通过业务控制每个“功能”例如注册、登录等的处理流程和相关逻辑。
2.流程先做什么再做什么。例如注册时需要先判断用户名是否被占用再决定是否完成注册。
3.逻辑能干什么不能干什么。例如注册时如果用户名被占用则不允许注册反之则允许注册。
4.业务的主要作用是保障数据安全和数据的完整性、有效性。
4.2 规划异常
1.关于异常
1.列举不少于十种异常
ThrowableErrorOutOfMemoryError(OOM)ExceptionSQLExceptionIOExceptionFileNotFoundExceptionRuntimeExceptionNullPointerExceptionArithmeticExceptionClassCastExceptionIndexOutOfBoundsExceptionArrayIndexOutOfBoundsExceptionStringIndexOutOfBoundsException2.异常的处理方式和处理原则
异常的处理方式有捕获处理(try…catch…finally)声明抛出(throw/throws)。如果当前方法适合处理则捕获处理如果当前方法不适合处理则声明抛出。
2.异常规划
1.为了便于统一管理自定义异常应先创建com.cy.store.service.ex.ServiceException自定义异常的基类异常继承自RuntimeException类并从父类生成子类的五个构造方法。
package com.cy.store.service.ex;/** 业务异常的基类 */
public class ServiceException extends RuntimeException {// Override Methods...
}2.当用户进行注册时可能会因为用户名被占用而导致无法正常注册此时需要抛出用户名被占用的异常因此可以设计一个用户名重复的com.cy.store.service.ex.UsernameDuplicateException异常类继承自ServiceException类并从父类生成子类的五个构造方法。
package com.cy.store.service.ex;/** 用户名重复的异常 */
public class UsernameDuplicateException extends ServiceException {// Override Methods...
}3.在用户进行注册时会执行数据库的INSERT操作该操作也是有可能失败的。则创建cn.tedu.store.service.ex.InsertException异常类继承自ServiceException类并从父类生成子类的5个构造方法。
package com.cy.store.service.ex;/** 插入数据的异常 */
public class InsertException extends ServiceException {// Override Methods...
}4.所有的自定义异常都应是RuntimeException的子孙类异常。项目中目前异常的继承结构是见下。
RuntimeException-- ServiceException-- UsernameDuplicateException-- InsertException4.3 接口与抽象方法
1.先创建com.cy.store.service.IUserService业务层接口并在接口中添加抽象方法。
package com.cy.store.service;
import com.cy.store.entity.User;/** 处理用户数据的业务层接口 */
public interface IUserService {/*** 用户注册* param user 用户数据*/void reg(User user);
}2.创建业务层接口目的是为了解耦。关于业务层的抽象方法的设计原则。
1.仅以操作成功为前提来设计返回值类型不考虑操作失败的情况
2.方法名称可以自定义通常与用户操作的功能相关
3.方法的参数列表根据执行的具体业务功能来确定需要哪些数据就设计哪些数据。通常情况下参数需要足以调用持久层对应的相关功能同时还要满足参数是客户端可以传递给控制器的
4.方法中使用抛出异常的方式来表示操作失败。4.4 实现抽象方法
1.创建com.cy.store.service.impl.UserServiceImpl业务层实现类并实现IUserService接口。在类之前添加Service注解并在类中添加持久层UserMapper对象。
package com.cy.store.service.impl;
import com.cy.store.entity.User;
import com.cy.store.mapper.UserMapper;
import com.cy.store.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;/** 处理用户数据的业务层实现类 */
Service
public class UserServiceImpl implements IUserService {Autowiredprivate UserMapper userMapper;Overridepublic void reg(User user) {// TODO}
}2.UserServiceImpl类需要重写IUserService接口中的抽象方法实现步骤大致是
Override
public void reg(User user) {// 根据参数user对象获取注册的用户名// 调用持久层的User findByUsername(String username)方法根据用户名查询用户数据// 判断查询结果是否不为null// 是表示用户名已被占用则抛出UsernameDuplicateException异常// 创建当前时间对象// 补全数据加密后的密码// 补全数据盐值// 补全数据isDelete(0)// 补全数据4项日志属性// 表示用户名没有被占用则允许注册// 调用持久层Integer insert(User user)方法执行注册并获取返回值(受影响的行数)// 判断受影响的行数是否不为1// 是插入数据时出现某种错误则抛出InsertException异常}3.reg()方法的具体实现过程。
Service
public class UserServiceImpl implements IUserService {Autowiredprivate UserMapper userMapper;Overridepublic void reg(User user) {// 根据参数user对象获取注册的用户名String username user.getUsername();// 调用持久层的User findByUsername(String username)方法根据用户名查询用户数据User result userMapper.findByUsername(username);// 判断查询结果是否不为nullif (result ! null) {// 是表示用户名已被占用则抛出UsernameDuplicateException异常throw new UsernameDuplicateException(尝试注册的用户名[ username ]已经被占用);}// 创建当前时间对象Date now new Date();// 补全数据加密后的密码String salt UUID.randomUUID().toString().toUpperCase();String md5Password getMd5Password(user.getPassword(), salt);user.setPassword(md5Password);// 补全数据盐值user.setSalt(salt);// 补全数据isDelete(0)user.setIsDelete(0);// 补全数据4项日志属性user.setCreatedUser(username);user.setCreatedTime(now);user.setModifiedUser(username);user.setModifiedTime(now);// 表示用户名没有被占用则允许注册// 调用持久层Integer insert(User user)方法执行注册并获取返回值(受影响的行数)Integer rows userMapper.insert(user);// 判断受影响的行数是否不为1if (rows ! 1) {// 是插入数据时出现某种错误则抛出InsertException异常throw new InsertException(添加用户数据出现未知错误请联系系统管理员);}}/*** 执行密码加密* param password 原始密码* param salt 盐值* return 加密后的密文*/private String getMd5Password(String password, String salt) {/** 加密规则* 1、无视原始密码的强度* 2、使用UUID作为盐值在原始密码的左右两侧拼接* 3、循环加密3次*/for (int i 0; i 3; i) {password DigestUtils.md5DigestAsHex((salt password salt).getBytes()).toUpperCase();}return password;}
}4.完成后在src/test/java下创建com.cy.store.service.UserServiceTests测试类编写并执行用户注册业务层的单元测试。
RunWith(SpringRunner.class)
SpringBootTest
public class UserServiceTests {Autowiredprivate IUserService iUserService;Testpublic void reg() {try {User user new User();user.setUsername(lower);user.setPassword(123456);user.setGender(1);user.setPhone(17858802974);user.setEmail(lowertedu.cn);user.setAvatar(xxxx);iUserService.reg(user);System.out.println(注册成功);} catch (ServiceException e) {System.out.println(注册失败 e.getClass().getSimpleName());System.out.println(e.getMessage());}}
}4.5 密码加密介绍
密码加密可以有效的防止数据泄密后带来的账号安全问题。通常程序员不需要考虑加密过程中使用的算法因为已经存在非常多成熟的加密算法可以直接使用。但是所有的加密算法都不适用于对密码进行加密因为加密算法都是可以进行逆向运算的。即如果能够获取加密过程中所有的参数就可以根据密文得到原文。
对密码进行加密时需使用消息摘要算法。消息摘要算法的特点是
1.原文相同时使用相同的摘要算法得到的摘要数据一定相同
2.使用相同的摘要算法进行运算无论原文的长度是多少得到的摘要数据长度是固定的
3.如果摘要数据相同则原文几乎相同但也可能不同可能性极低。不同的原文在一定的概率上能够得到相同的摘要数据发生这种现象时称为碰撞。
以MD5算法为例运算得到的结果是128位的二进制数。在密码的应用领域中通常会限制密码长度的最小值和最大值可是密码的种类是有限的发生碰撞在概率上可以认为是不存在的。
常见的摘要算法有SHA(Secure Hash Argorithm)家族和MD(Message Digest)系列的算法。
关于MD5算法的破解主要来自两方面。一个是王小云教授的破解学术上的破解其实是研究消息摘要算法的碰撞也就是更快的找到两个不同的原文却对应相同的摘要并不是假想中的“根据密文逆向运算得到原文”。另一个是所谓的“在线破解”是使用数据库记录大量的原文与摘要的对应关系当尝试“破解”时本质上是查询这个数据库根据摘要查询原文。
为进一步保障密码安全需满足以下加密规则
1.要求用户使用安全强度更高的原始密码
2.加盐
3.多重加密
4.综合以上所有应用方式。5 用户-注册-控制器
5.1 创建响应结果类
创建com.cy.store.util.JsonResult响应结果类型。
package com.cy.store.util;
import java.io.Serializable;/*** 响应结果类* param E 响应数据的类型*/
public class JsonResultE implements Serializable {/** 状态码 */private Integer state;/** 状态描述信息 */private String message;/** 数据 */private E data;public JsonResult() {super();}public JsonResult(Integer state) {super();this.state state;}/** 出现异常时调用 */public JsonResult(Throwable e) {super();// 获取异常对象中的异常信息this.message e.getMessage();}public JsonResult(Integer state, E data) {super();this.state state;this.data data;}// Generate: Getter and Setter
}5.2 设计请求
设计用户提交的请求并设计响应的方式
请求路径/users/reg
请求参数User user
请求类型POST
响应结果JsonResultVoid5.3 处理请求
1.创建com.cy.store.controller.UserController控制器类在类的声明之前添加RestController和RequestMapping(“users”)注解在类中添加IUserService业务对象并使用Autowired注解修饰。
package com.cy.store.controller;
import com.cy.store.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/** 处理用户相关请求的控制器类 */
RestController
RequestMapping(users)
public class UserController {Autowiredprivate IUserService userService;
}2.然后在类中添加处理请求的用户注册方法。
RequestMapping(reg)
public JsonResultVoid reg(User user) {// 创建返回值JsonResultVoid result new JsonResultVoid();try {// 调用业务对象执行注册userService.reg(user);// 响应成功result.setState(200);} catch (UsernameDuplicateException e) {// 用户名被占用result.setState(4000);result.setMessage(用户名已经被占用);} catch (InsertException e) {// 插入数据异常result.setState(5000);result.setMessage(注册失败请联系系统管理员);}return result;
}3.完成后启动项目打开浏览器访问http://localhost:8080/users/reg?usernamecontrollerpassword123456请求进行测试。
{state: 200,message: null,data: null
}5.4 控制器层的调整
1.然后创建提供控制器类的基类com.cy.store.controller.BaseController在其中定义表示响应成功的状态码及统一处理异常的方法。 ExceptionHandler注解用于统一处理方法抛出的异常。当我们使用这个注解时需要定义一个异常的处理方法再给这个方法加上ExceptionHandler注解这个方法就会处理类中其他方法被RequestMapping注解抛出的异常。ExceptionHandler注解中可以添加参数参数是某个异常类的class代表这个方法专门处理该类异常。 package com.cy.store.controller;
import com.cy.store.service.ex.InsertException;
import com.cy.store.service.ex.ServiceException;
import com.cy.store.service.ex.UsernameDuplicateException;
import com.cy.store.util.JsonResult;
import org.springframework.web.bind.annotation.ExceptionHandler;/** 控制器类的基类 */
public class BaseController {/** 操作成功的状态码 */public static final int OK 200;/** ExceptionHandler用于统一处理方法抛出的异常 */ExceptionHandler(ServiceException.class)public JsonResultVoid handleException(Throwable e) {JsonResultVoid result new JsonResultVoid(e);if (e instanceof UsernameDuplicateException) {result.setState(4000);} else if (e instanceof InsertException) {result.setState(5000);}return result;}
}2.最后简化UserController控制器类中的用户注册reg()方法的代码。
/** 处理用户相关请求的控制器类 */
RestController
RequestMapping(users)
public class UserController extends BaseController {Autowiredprivate IUserService userService;RequestMapping(reg)public JsonResultVoid reg(User user) {// 调用业务对象执行注册userService.reg(user);// 返回return new JsonResultVoid(OK);}
}3.完成后启动项目打开浏览器访问http://localhost:8080/users/reg?usernamecontrollerpassword123456请求进行测试。
6 用户-注册-前端页面
1.将电脑商城前端资源页面pages文件夹下的静态资源bootstrap3、css、images、js、web、index.html相关的资源复制到项目src/main/resources/static文件夹下。如图所示。 2.在register.html页面中body标签内部的最后添加script标签用于编写JavaScript程序。请求的url中需要添加项目的访问名称。 serialize()方法通过序列化表单值创建URL编码文本字符串。 script typetext/javascript$(#btn-reg).click(function() {console.log($(#form-reg).serialize());$.ajax({url: /users/reg,type: POST,data: $(#form-reg).serialize(),dataType: json,success: function(json) {if (json.state 200) {alert(注册成功);// location.href login.html;} else {alert(注册失败 json.message);}}});});
/script3.完成后启动项目打开浏览器访问http://localhost:8080/web/register.html页面并进行注册。 注意由于没有验证数据即使没有填写用户名或密码也可以注册成功。 用户登录
1 用户-登录-持久层
1.1 规划需要执行的SQL语句
用户登录功能需要执行的SQL语句是根据用户名查询用户数据再判断密码是否正确。SQL语句大致是
SELECT * FROM t_user WHERE username?说明以上SQL语句对应的后台开发已经完成无需再次开发。 1.2 接口与抽象方法 说明无需再次开发。 1.3 配置SQL映射 说明无需再次开发。 2 用户-登录-业务层
2.1 规划异常
1.如果用户名不存在则登录失败抛出com.cy.store.service.ex.UserNotFoundException异常并从父类生成子类的五个构造方法。
package com.cy.store.service.ex;/** 用户数据不存在的异常 */
public class UserNotFoundException extends ServiceException {// Override Methods...
}2.如果用户的isDelete字段的值为1则表示当前用户数据被标记为“已删除”需进行登录失败操作同时抛出UserNotFoundException。
3.如果密码错误则进行登录失败操作同时抛出com.cy.store.service.ex.PasswordNotMatchException异常。
package com.cy.store.service.ex;/** 密码验证失败的异常 */
public class PasswordNotMatchException extends ServiceException {// Override Methods...
}4.创建以上UserNotFoundException和PasswordNotMatchException异常类以上异常类应继承自ServiceException类。
2.2 接口与抽象方法
在IUserService接口中添加登录功能的抽象方法。
/*** 用户登录* param username 用户名* param password 密码* return 登录成功的用户数据*/
User login(String username, String password);当登录成功后需要获取该用户的id以便于后续识别该用户的身份并且还需要获取该用户的用户名、头像等数据用于显示在软件的界面中需使用可以封装用于id、用户名和头像的数据的类型来作为登录方法的返回值类型。 2.3 实现抽象方法
1.在UserServiceImpl类中添加login(String username, String password)方法并分析业务逻辑。
Override
public User login(String username, String password) {// 调用userMapper的findByUsername()方法根据参数username查询用户数据// 判断查询结果是否为null// 是抛出UserNotFoundException异常// 判断查询结果中的isDelete是否为1// 是抛出UserNotFoundException异常// 从查询结果中获取盐值// 调用getMd5Password()方法将参数password和salt结合起来进行加密// 判断查询结果中的密码与以上加密得到的密码是否不一致// 是抛出PasswordNotMatchException异常// 创建新的User对象// 将查询结果中的uid、username、avatar封装到新的user对象中// 返回新的user对象return null;
}2.login(String username, String password)方法中代码的具体实现。
Override
public User login(String username, String password) {// 调用userMapper的findByUsername()方法根据参数username查询用户数据User result userMapper.findByUsername(username);// 判断查询结果是否为nullif (result null) {// 是抛出UserNotFoundException异常throw new UserNotFoundException(用户数据不存在的错误);}// 判断查询结果中的isDelete是否为1if (result.getIsDelete() 1) {// 是抛出UserNotFoundException异常throw new UserNotFoundException(用户数据不存在的错误);}// 从查询结果中获取盐值String salt result.getSalt();// 调用getMd5Password()方法将参数password和salt结合起来进行加密String md5Password getMd5Password(password, salt);// 判断查询结果中的密码与以上加密得到的密码是否不一致if (!result.getPassword().equals(md5Password)) {// 是抛出PasswordNotMatchException异常throw new PasswordNotMatchException(密码验证失败的错误);}// 创建新的User对象User user new User();// 将查询结果中的uid、username、avatar封装到新的user对象中user.setUid(result.getUid());user.setUsername(result.getUsername());user.setAvatar(result.getAvatar());// 返回新的user对象return user;
}3.完成后在UserServiceTests中编写并完成单元测试。
Test
public void login() {try {String username lower;String password 123456;User user iUserService.login(username, password);System.out.println(登录成功 user);} catch (ServiceException e) {System.out.println(登录失败 e.getClass().getSimpleName());System.out.println(e.getMessage());}注意不要使用错误的数据尝试登录例如早期通过持久层测试新增用户的数据将这些数据从表中删除。 3 用户-登录-控制器
3.1 处理异常
处理用户登录功能时在业务层抛出了UserNotFoundException和PasswordNotMatchException异常而这两个异常均未被处理过。则应在BaseController类的处理异常的方法中添加这两个分支进行处理。
ExceptionHandler(ServiceException.class)
public JsonResultVoid handleException(Throwable e) {JsonResultVoid result new JsonResultVoid(e);if (e instanceof UsernameDuplicateException) {result.setState(4000);} else if (e instanceof UserNotFoundException) {result.setState(4001);} else if (e instanceof PasswordNotMatchException) {result.setState(4002);} else if (e instanceof InsertException) {result.setState(5000);}return result;
}3.2 设计请求
设计用户提交的请求并设计响应的方式
请求路径/users/login
请求参数String username, String password
请求类型POST
响应结果JsonResultUser3.3 处理请求
1.在UserController类中添加处理登录请求的login(String username, String password)方法。
RequestMapping(login)
public JsonResultUser login(String username, String password) {// 调用业务对象的方法执行登录并获取返回值// 将以上返回值和状态码OK封装到响应结果中并返回return null;
}2.处理登录请求的login(String username, String password)方法代码具体实现。
RequestMapping(login)
public JsonResultUser login(String username, String password) {// 调用业务对象的方法执行登录并获取返回值User data userService.login(username, password);// 将以上返回值和状态码OK封装到响应结果中并返回return new JsonResultUser(OK, data);
}4.完成后启动项目访问http://localhost:8080/users/login?usernameTompassword1234请求进行登录。 4 用户-登录-前端页面
1.在login.html页面中body标签内部的最后添加script标签用于编写JavaScript程序。
script typetext/javascript$(#btn-login).click(function() {$.ajax({url: /users/login,type: POST,data: $(#form-login).serialize(),dataType: json,success: function(json) {if (json.state 200) {alert(登录成功);location.href index.html;} else {alert(登录失败 json.message);}}});
});
/script2.完成后启动项目打开浏览器访问http://localhost:8080/web/login.html页面并进行登录。
拦截器
在Spring MVC中拦截请求是通过处理器拦截器HandlerInterceptor来实现的它拦截的目标是请求的地址。在Spring MVC中定义一个拦截器需要实现HandlerInterceptor接口。
1 HandlerInterceptor
1.1 preHandle()方法
该方法将在请求处理之前被调用。SpringMVC中的Interceptor是链式的调用在一个应用或一个请求中可以同时存在多个Interceptor。每个Interceptor的调用会依据它的声明顺序依次执行而且最先执行的都是Interceptor中的preHandle()方法所以可以在这个方法中进行一些前置初始化操作或者是对当前请求的一个预处理也可以在这个方法中进行一些判断来决定请求是否要继续进行下去。该方法的返回值是布尔值类型当返回false时表示请求结束后续的Interceptor和Controller都不会再执行当返回值true时就会继续调用下一个Interceptor的preHandle方法如果已经是最后一个Interceptor的时就会调用当前请求的Controller方法。
1.2 postHandle()方法
该方法将在当前请求进行处理之后也就是Controller方法调用之后执行但是它会在DispatcherServlet进行视图返回渲染之前被调用所以我们可以在这个方法中对Controller处理之后的ModelAndView对象进行操作。postHandle方法被调用的方向跟preHandle是相反的也就是说先声明的Interceptor的postHandle方法反而会后执行。如果当前Interceptor的preHandle()方法返回值为false则此方法不会被调用。
1.3 afterCompletion()方法
该方法将在整个当前请求结束之后也就是在DispatcherServlet渲染了对应的视图之后执行。这个方法的主要作用是用于进行资源清理工作。如果当前Interceptor的preHandle()方法返回值为false则此方法不会被调用。
2 WebMvcConfigurer
在SpringBoot项目中如果想要自定义一些Interceptor、ViewResolver、MessageConverter该如何实现呢在SpringBoot 1.5版本都是靠重写WebMvcConfigurerAdapter类中的方法来添加自定义拦截器、视图解析器、消息转换器等。而在SpringBoot 2.0版本之后该类被标记为Deprecated。因此我们只能靠实现WebMvcConfigurer接口来实现。
WebMvcConfigurer接口中的核心方法之一addInterceptors(InterceptorRegistry registry)方法表示添加拦截器。主要用于进行用户登录状态的拦截日志的拦截等。 addInterceptor需要一个实现HandlerInterceptor接口的拦截器实例 addPathPatterns用于设置拦截器的过滤路径规则addPathPatterns(“/**”)对所有请求都拦截 excludePathPatterns用于设置不需要拦截的过滤规则
public interface WebMvcConfigurer {// ...default void addInterceptors(InterceptorRegistry registry) {}
}3 项目添加拦截器功能
1.分析项目中很多操作都是需要先登录才可以执行的如果在每个请求处理之前都编写代码检查Session中有没有登录信息是不现实的。所以应使用拦截器解决该问题。
2.创建拦截器类com.cy.store.interceptor.LoginInterceptor并实现org.springframework.web.servlet.HandlerInterceptor接口。
package com.cy.store.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/** 定义处理器拦截器 */
public class LoginInterceptor implements HandlerInterceptor {Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {if (request.getSession().getAttribute(uid) null) {response.sendRedirect(/web/login.html);return false;}return true;}
}3.创建LoginInterceptorConfigurer拦截器的配置类并实现org.springframework.web.servlet.config.annotation.WebMvcConfigurer接口配置类需要添加Configruation注解修饰。
package com.cy.store.config;
import com.cy.store.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.ArrayList;
import java.util.List;/** 注册处理器拦截器 */
Configuration
public class LoginInterceptorConfigurer implements WebMvcConfigurer {/** 拦截器配置 */Overridepublic void addInterceptors(InterceptorRegistry registry) {// 创建拦截器对象HandlerInterceptor interceptor new LoginInterceptor();// 白名单ListString patterns new ArrayListString();patterns.add(/bootstrap3/**);patterns.add(/css/**);patterns.add(/images/**);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);patterns.add(/districts/**);patterns.add(/products/**);// 通过注册工具添加拦截器registry.addInterceptor(interceptor).addPathPatterns(/**).excludePathPatterns(patterns);}
}会话
1.重新构建login()方法登录成功后将uid和username存入到HttpSession对象中。
RequestMapping(login)
public JsonResultUser login(String username, String password, HttpSession session) {// 调用业务对象的方法执行登录并获取返回值User data userService.login(username, password);//登录成功后将uid和username存入到HttpSession中session.setAttribute(uid, data.getUid());session.setAttribute(username, data.getUsername());// System.out.println(Session中的uid getUidFromSession(session));// System.out.println(Session中的username getUsernameFromSession(session));// 将以上返回值和状态码OK封装到响应结果中并返回return new JsonResultUser(OK, data);
}2.在父类BaseController中添加从HttpSession对象中获取uid和username的方法以便于后续快捷的获取这两个属性的值。
/*** 从HttpSession对象中获取uid* param session HttpSession对象* return 当前登录的用户的id*/
protected final Integer getUidFromSession(HttpSession session) {return Integer.valueOf(session.getAttribute(uid).toString());
}/*** 从HttpSession对象中获取用户名* param session HttpSession对象* return 当前登录的用户名*/
protected final String getUsernameFromSession(HttpSession session) {return session.getAttribute(username).toString();
}ping(login)
public JsonResultUser login(String username, String password, HttpSession session) {
// 调用业务对象的方法执行登录并获取返回值User data userService.login(username, password);//登录成功后将uid和username存入到HttpSession中session.setAttribute(uid, data.getUid());session.setAttribute(username, data.getUsername());// System.out.println(Session中的uid getUidFromSession(session));// System.out.println(Session中的username getUsernameFromSession(session));// 将以上返回值和状态码OK封装到响应结果中并返回return new JsonResultUser(OK, data);
}2.在父类BaseController中添加从HttpSession对象中获取uid和username的方法以便于后续快捷的获取这两个属性的值。
/*** 从HttpSession对象中获取uid* param session HttpSession对象* return 当前登录的用户的id*/
protected final Integer getUidFromSession(HttpSession session) {return Integer.valueOf(session.getAttribute(uid).toString());
}/*** 从HttpSession对象中获取用户名* param session HttpSession对象* return 当前登录的用户名*/
protected final String getUsernameFromSession(HttpSession session) {return session.getAttribute(username).toString();
}