网上商城项目(一)"/>
SpringBoot网上商城项目(一)
SpringBoot网上商城项目
一、项目环境搭建
1.项目分析
1、项目功能:登录,注册,热销商品,用户管理(密码,个人信息,头像,收货地址),购物车(展示,增加,删除),订单模块
2.开发顺序:注册,登录,用户管理,购物车,商品,订单模块
3.某一个模块的开发顺序:
持久层开发:依据前端页面的需求规划相关的SQL语句,以及进行配置
业务层开发:核心功能控制,业务操作以及异常的处理
控制层开发:接收请求,处理响应
前端开发:JS,Query,AJAX这些技术来连接后台
2.项目基本环境
- JDK:1.8版本及以上
- maven:需要配置到idea,3.6.1版本及以上
- 数据库:MariaDB,MySQL,要求是5.1版本及以上
- 开发的平台:idea开发
- 项目名称:store,表示商城
- 结构:com.cy.store
- 资源文件:resources文件夹下(static,templates)
- 单元测试:test.cy.store
二、用户注册功能
1.创建数据表
1.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)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
2.创建用户实体类
1.通过表结构提取表公共字段,存放在实体类的基类中,取名BaseEntity。
@Data
@AllArgsConstructor
@NoArgsConstructor
//实体类基类,只要是实体类,要满足几个约束,实现接口Serializable,序列化
public class BaseEntity implements Serializable {//把下滑线改成大写
// private String created_user;private String createdUser;private Date createdTime;private String modifiedUser;private Date modifiedTime;
}
2.创建用户的实体类User,需要继承BaseEntity
@NoArgsConstructor
@AllArgsConstructor
@Data
//用户实体类 SpringBoot 约定大于配置
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;
}
3.持久层开发
1.规划需要执行的SQL语句
用户注册功能,相当于数据的插入操作
insert into t_user(username,password,....) values (值列表)
在用户的注册时首先要查询当前用户名是否存在,如果存在则不能在注册
select * from t_user where username=?
2.设计接口和方法
1.在项目的目录结构创建一个mapper,创建一个UserMapper接口,在接口中定义SQL语句抽象方法
@Mapper
public interface UserMapper {/** 插入用户的数据* user : 用户的数据* @return 受影响的行数(增删改都受影响的行数作为返回值)* */Integer insert(User user);/** 根据用户名来查询用户的数据* username : 用户名* return : 如果找到对应的用户则返回用户的数据,没有找到则返回null* */User findByUsername(String username);}
2.在pom.xml中添加
<build><resources><resource><directory>src/main/java</directory><includes><include>**/*.xml</include></includes></resource><resource><directory>src/main/resources</directory></resource></resources>
</build>
3.在启动类配置mapper接口文件的位置
@MapperScan("com.cy.store.mapper")
3.编写映射
1.在mapper包下创建接口对应的映射文件,遵循和接口的名称一致,创建一个UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis//DTD Mapper 3.0//EN"".dtd">
<mapper namespace="com.scy.store.mapper.UserMapper"></mapper>
2.在application开启驼峰命名法,就不需要自己定义映射规则
mybatis.configuration.map-underscore-to-camel-case=true
2.配置接口中的方法对应SQl语句
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis//DTD Mapper 3.0//EN"".dtd">
<!--namespace属性:用于指定当前的映射文件和那个接口进行映射,需要指定接口的文件路径-->
<mapper namespace="com.cy.store.mapper.UserMapper"><!-- id属性:表示映射的接口中方法的名称,直接用标签的内容来编写sql语句,这里的insert 对应UserMapper中的insert方法useGeneratedKeys="true" :打开自增 keyProperty="uid“ 让uid进行自增 --><insert id="insert" useGeneratedKeys="true" keyProperty="uid">insert into t_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><select id="findByUsername" resultType="com.cy.store.entity.User">select * from t_user where username = #{username}</select></mapper>
3.单元测试
在test下store创建一个mapper包,在mapper包下创建一个UserMapperTests类
@SpringBootTest
public class UserMapperTests {@Autowiredprivate UserMapper userMapper;/*单元测试方法:* 1.必须被@Test注解修饰* 2.返回值类型必须是void* 3.方法的参数列表不指定任何类型* 4.方法的访问修饰符必须是public*/@Testpublic void insert(){User user=new User();user.setUsername("tom");user.setPassword("123");Integer rows = userMapper.insert(user);System.out.println(rows);}@Testpublic void findByUsername(){User user = userMapper.findByUsername("tom");System.out.println(user);}
}
4.业务层开发
- 接口直接写在service包下
- service包下创建ex包用来写异常类
- service包下创建impl包用来写接口的实现类
1.规划异常
RuntimeException异常作为父类,创建一个业务层异常的基类,起名ServiceException异常,并使其继承RuntimeException异常。
根据业务层不同的功能来详细定义具体的异常类型,统一去继承ServiceException异常类
//业务层异常的基类
public class ServiceException extends RuntimeException{public ServiceException() {super();}public ServiceException(String message) {super(message);}public ServiceException(String message, Throwable cause) {super(message, cause);}public ServiceException(Throwable cause) {super(cause);}protected ServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {super(message, cause, enableSuppression, writableStackTrace);}
}
1.用户在进行注册时候可能会产生用户名被占用错误,抛出一个异常:UsernameDuplicatedException异常
//用户名被占用异常
public class UsernameDuplicatedException extends ServiceException {public UsernameDuplicatedException() {super();}public UsernameDuplicatedException(String message) {super(message);}public UsernameDuplicatedException(String message, Throwable cause) {super(message, cause);}public UsernameDuplicatedException(Throwable cause) {super(cause);}protected UsernameDuplicatedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {super(message, cause, enableSuppression, writableStackTrace);}
}
2.正在执行数据插入操作的时候,服务器,数据库断开。处于正在插入过程产生的异常InsertException异常
//数据在插入所产生的异常
public class InsertException extends ServiceException{public InsertException() {super();}public InsertException(String message) {super(message);}public InsertException(String message, Throwable cause) {super(message, cause);}public InsertException(Throwable cause) {super(cause);}protected InsertException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {super(message, cause, enableSuppression, writableStackTrace);}
}
2、设计接口和抽象方法
1.IUserService接口:
//用户模块业务层接口
public interface IUserService {/** 用户注册方法* user:用户的数据对象*/void reg(User user);
}
2.UserServiceImpl实现类去实现IUserService接口,使用md5进行加密
//用户模块业务层实现类
@Service
public class UserServiceImpl implements IUserService {@Autowiredprivate UserMapper userMapper;@Overridepublic void reg(User user) {// 调用findByUsername(usernam) 判断用户是否注册String username = user.getUsername();User result = userMapper.findByUsername(username);//判断结果是否为null,为null则可以注册,不为null则抛出用户名被占用的异常if (result!=null){throw new UsernameDuplicatedException("用户名被占用");}//密码加密处理 :md5算法 盐值+password+盐值 在进行md5加密String oldPassword = user.getPassword();//随机生成盐值String salt = UUID.randomUUID().toString().toUpperCase();//要将salt记录下来user.setSalt(salt);//将密码和盐值作为整体进行加密String md5Password = getMd5Password(oldPassword, salt);//将加密之后的密码重新补全设置到user中的passworduser.setPassword(md5Password);//补全数据: is_delete设置为0user.setIsDelete(0);//补全数据:4个日志字段信息user.setCreatedUser(user.getUsername());user.setModifiedUser(user.getUsername());Date date = new Date();user.setCreatedTime(date);user.setModifiedTime(date);//执行注业务功能的实现,插入成功则rows=1Integer rows = userMapper.insert(user);if (rows!=1){throw new InsertException("在用户注册过程中产生未知异常");}}//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;}
}
3.单元测试
@SpringBootTest
public class UserServiceTests {@Autowiredprivate IUserService userService;@Testpublic void reg(){try {User user=new User();user.setUsername("ab");user.setPassword("124");userService.reg(user);} catch (ServiceException e) {//获取异常的对象和类的名称System.out.println(e.getMessage());System.out.println(e.getClass().getSimpleName());}}
}
5.控制层开发
1.创建响应
状态码,状态描述信息,数据,这部分功能封装在一个类中,将这类作为返回值,返回给前端浏览器。
创建一个util包,在包下创建JsonResult
//Json格式的数据进行响应
@Data
public class JsonResult<E> implements Serializable {//状态码private Integer state;
// 描写信息private String message;//数据private E data;public JsonResult() {}public JsonResult(Integer state) {this.state = state;}public JsonResult(Throwable e) {this.message=e.getMessage();}public JsonResult(Integer state, E data) {this.state = state;this.data = data;}
}
2.设计请求
依据当前业务功能的模块进行请求设计
请求路径:/users/reg
请求参数:User user
请求类型: POST GET
响应结果:JsonResult<void>
3.请求处理
1.在controller层创BaseController,去统一处理异常相关操作。
//控制层类的基类
public class BaseController {//用他来表示操作成功的状态码public static final int OK=200;//请求处理方法,这个方法的返回值需要传递给前端的数据@ExceptionHandler(ServiceException.class) //用于统一处理抛出的异常,凡是关于ServiceException的异常,都在这里处理public JsonResult<Void> handleException(Throwable e){JsonResult<Void> result = new JsonResult<>(e);if (e instanceof UsernameDuplicatedException){result.setState(4000);result.setMessage("用户名被占用");}else if (e instanceof InsertException){result.setState(5000);result.setMessage("注册时产生未知异常!");}return result;}
}
1.UserController:
@RestController
@RequestMapping("users")
public class UserController extends BaseController{@Autowiredprivate IUserService userService;@RequestMapping("reg")public JsonResult<Void> reg(User user){userService.reg(user);return new JsonResult<>(OK);}}
6.前端页面开发
1.在register页面中编写发送请求的方法,当用户点击注册时发送请求,用点击事件来完成,$.ajax()函数发送异步请求。
2.ajax()
$.ajax({//url:请求的地址url:"",//请求的类型(GET,POST) type:"POST"type:"",//向指定的请求url地址提交的数据data:"",//提交的数据的类型,一般为json类型 dataType:"json"dataType:"",//服务器正常响应客户端时,会自动调用success参数方法,并且将服务器返回的数据以参数形式传递个这个方法success:function(){},//相反error:function(){}});
3.在register.html中添加
<script type="text/javascript">//1.监听注册按钮点击事件$("#btn-reg").click(function () {$.ajax({url:"/users/reg",type:"POST",//获取表单id:form-regdata:$("#form-reg").serialize(),dataType:"JSON",success:function (json){if (json.state == 200){alert("注册成功")}else {alert("注册失败")}},error:function (xhr){alert("注册产生未知异常"+xhr.status)}});});</script>
三、登陆功能开发
1.持久层
依据用户提交的用户名和密码进行select查询
select * form t_user where username=?
2.业务层
1.规划异常
1.用户名对应密码错误,密码匹配失败异常:PasswordNotMatchException异常。继承业务层异常
//密码错误异常
public class PasswordNotMatchException extends ServiceException{public PasswordNotMatchException() {super();}public PasswordNotMatchException(String message) {super(message);}public PasswordNotMatchException(String message, Throwable cause) {super(message, cause);}public PasswordNotMatchException(Throwable cause) {super(cause);}protected PasswordNotMatchException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {super(message, cause, enableSuppression, writableStackTrace);}
}
2.用户名不存在,UsernameNotFoundException异常,继承业务层异常
//用户名不存在异常
public class UsernameNotFoundException extends ServiceException{public UsernameNotFoundException() {super();}public UsernameNotFoundException(String message) {super(message);}public UsernameNotFoundException(String message, Throwable cause) {super(message, cause);}public UsernameNotFoundException(Throwable cause) {super(cause);}protected UsernameNotFoundException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {super(message, cause, enableSuppression, writableStackTrace);}
}
2.设计接口和抽象方法
1.直接在IUserSerivce接口中编写抽象方法,(用户名,用户id存放在session中)
IUserService接口:
/*
* 用户登陆功能
* username:用户名
* password:用户密码
* return: 返回用户数据,没有则返回null*/
User login(String username,String password);
3.抽象方法实现
在UserServiceImpl中添加登陆方法:
//用户登陆
@Override
public User login(String username, String password) {//获取数据库中的用户数据User result = userMapper.findByUsername(username);if (result == null){throw new UsernameNotFoundException("用户不存在");}//检测用户的密码是否匹配//1.先获取到数据库中的加密之后的密码String oldPassword = result.getPassword();/*2.和用户传递过来的密码进行比较* 要先获取盐值,上一次注册生成的盐值* 将用户的密码进行md5算法规则进行加密,和注册时加密次数相同* */String salt = result.getSalt();String newMd5Password=getMd5Password(password,salt);//3.将密码进行比较if (!newMd5Password.equals(oldPassword)){throw new PasswordNotMatchException("用户密码错误");}//判断is_delete字段的值是否为1,为1表示被标记为删除,表示用户被注销if (result.getIsDelete()==1){throw new UsernameNotFoundException("用户数据不存在");}//创建一个新的User去储存需要用到的数据,减少没用的数据获取,提升系统性能User user = new User();user.setUid(result.getUid());user.setUsername(result.getUsername());user.setAvatar(result.getAvatar());return user;
}
3.控制层
1.处理异常
在BaseController新添加2个异常处理
//控制层类的基类
public class BaseController {//用他来表示操作成功的状态码public static final int OK=200;//请求处理方法,这个方法的返回值需要传递给前端的数据@ExceptionHandler(ServiceException.class) //用于统一处理抛出的异常,凡是关于ServiceException的异常,都在这里处理public JsonResult<Void> handleException(Throwable e){JsonResult<Void> result = new JsonResult<>(e);if (e instanceof UsernameDuplicatedException){result.setState(4000);result.setMessage("用户名被占用");}else if (e instanceof InsertException){result.setState(5000);result.setMessage("注册时产生未知异常!");}else if (e instanceof UsernameNotFoundException){result.setState(5001);result.setMessage("用户名不存在异常");}else if (e instanceof PasswordNotMatchException){result.setState(5002);result.setMessage("用户密码错误的异常");}return result;}
}
2.设计请求
请求路径:/users/login
请求方式:POST
请求数据:String username,String password,HttpSession session
响应结果:JsonResult<User>
3.处理请求
1.在BaseController中封装session对象中数据的获取、数据的设置
封装2个数据:获取uid和获取username对应的两个方法,
BaseController:
/*
* 获取session中的uid
* session:session对象
* return:当前登录用户的uid对象*/
protected final Integer getuidFromSession(HttpSession session){return Integer.valueOf(session.getAttribute("uid").toString());
}/*
* 获取session中的username
* return:返回当前登录用户的用户名*/
protected final String getUsernameFromSession(HttpSession session){return session.getAttribute("username").toString();
}
2.在登陆的方法中将数据封装在session对象中,服务器本身自动创建优session对象,已经是一个全局的session对象。
在UserController中login方法添加session
//登陆页面@RequestMapping("login")public JsonResult<User> login(String username, String password, HttpSession session){User data = userService.login(username, password);//向session对象中完成数据的绑定(session是全局的)session.setAttribute("uid",data.getUid());session.setAttribute("username",data.getUsername());return new JsonResult<>(OK,data);}
4.前端页面
1.在login.html页面中依据前面所设置的请求来发送ajax请求。
<script type="text/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("登陆失败")}},error:function (xhr) {alert("登陆产生未知异常"+xhr.message);}});});
</script>
6.拦截器
1.定义一个类,去实现HandlerInterceptor接口,创建一个interceptor包,在包下创建一个类 LoginInterceptor
//定义一个拦截器
public class LoginInterceptor implements HandlerInterceptor {/*** 检测全局session对象中是否有uid数据,如果有则放行,没有则重定向到登录页面* @param request 请求对象* @param response 响应对象* @param handler 处理器(url+Controller:映射)* @return 如果返回这为ture放行,返回值false则表示拦截当前请求* @throws Exception*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//HttpServletRequest对象来获取session对象Object obj=request.getSession().getAttribute("uid");if (obj==null){//说明用户没有登录过,重定向到login.htmlresponse.sendRedirect("/web/login.html");//返回false结束调用return false;}//放行return true;}
}
2.注册过滤器:添加白名单(哪些资源可以不登陆的情况下访问:login.html, register.html, index.html,produce.html,reg,login(商品的详情页面)),添加黑名单(在用户登录的状态才能访问)。
创建config包,在包下创建LoginInterceptorConfigurer
//拦截器的注册
@Configuration //放到配置类中
public class LoginInterceptorConfigurer implements WebMvcConfigurer {//将自定义的拦截器进行注册@Overridepublic void addInterceptors(InterceptorRegistry registry) {//创建自定义拦截器对象HandlerInterceptor interceptor=new LoginInterceptor();//配置白名单,存放在List集合中List<String> patterns=new ArrayList<>();//1.静态资源放行 2.一些页面放行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/product.html");patterns.add("/index.html");patterns.add("/users/reg");patterns.add("/users/login");/*** addInterceptor 添加注册* addPathPatterns 拦截的url* excludePathPatterns 除了哪些url不拦截*/registry.addInterceptor(interceptor).addPathPatterns("/**").excludePathPatterns(patterns);}
}
更多推荐
SpringBoot网上商城项目(一)
发布评论