前置介绍
项目介绍:基于SpringBoot+Mybatis+BootStrap3.0前后端不分离的项目
项目功能 :
①登录、注册、集成kaptcha验证码防止机器人,以及用户管理(用户信息的修改)
②分页处理展示商品信息、支持通过模糊搜索查找相关商品
③购物车相关功能管理(如购物车商品的展示和结算)
④订单管理页面可以查看不同状态订单以及集成了支付宝沙箱模拟商品支付全过程
⑤收藏界面同样实现分页展示用户收藏商品信息,以及加入收藏、取消收藏和加入购物车
⑥对全部业务方法使用aop拦截计算业务执行时间,为后续优化提供支持
环境搭建 基本环境:
1.jdk 1.8
2.maven 3.6.2
3.数据库 mysql 8.0.26
4.集成开发环境 IDEA2020
用户管理 创建数据库 1 CREATE DATABASE IF NOT EXISTS `computer_store` CHARACTER SET 'utf8';
创建数据表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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;
考虑到每个表中都有固定的四个字段,重复写过于麻烦,因此可以使用一个Java基类来对应这四个字段
创建实体类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Data public class BaseEntity implements Serializable { private String createdUser; private Date createdTime; private String modifiedUser; private Date modifiedTime; } @Data public class User extends BaseEntity { 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; }
注册 后端-持久层
1.编写sql语句
1 2 3 4 5 #增加用户的sql语句 insert into t_user(username,password) vaules(?,?); #判断用户名是否已存在的语句 select * from t_user where username = ?;
2.定义mapper接口和抽象方法
由于项目可能有多个mapper接口,因此在项目的目录结构下创建一个mapper的包,用于管理所有的mapper接口,
并在springboot启动类上使用@MapperScan注解扫描此包下的所有mapper接口或直接在接口上使用@Mapper注解。
以便在项目启动的时间把mapper扫描进容器中,交由spring容器管理。
1 2 3 4 5 6 7 8 public interface UserMapper { int addUser (User user) ; User queryUserByUsername (String username) ; }
3.编写映射文件
项目可能有多个mapper映射文件,必须在项目目录结构resource文件下创建一个mapper的包。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="top.year21.computerstore.mapper.UserMapper" > <insert id ="addUser" > 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 > <resultMap id ="queryUser" type ="user" > <id property ="uid" column ="uid" /> <result property ="username" column ="username" /> <result property ="password" column ="password" /> <result property ="salt" column ="salt" /> <result property ="phone" column ="phone" /> <result property ="gender" column ="gender" /> <result property ="avatar" column ="avatar" /> <result property ="isDelete" column ="is_delete" /> <result property ="createdUser" column ="created_user" /> <result property ="createdTime" column ="created_time" /> <result property ="modifiedUser" column ="modified_user" /> <result property ="modifiedTime" column ="modified_time" /> </resultMap > <select id ="queryUserByUsername" resultMap ="queryUser" > select * from t_user where username = #{username} </select > </mapper >
4.将mapper映射文件的位置在yml配置文件中进行对应的设置
1 2 mybatis: mapper-locations: classpath:mybatis/mapper/*.xml
5.进行单元测试(每个 的完成建议都进行单元测试)
后端-业务层 业务层的包下主要有exception、impl、接口,其中exception放置各种异常处理类,impl中放置接口的实现类
1.规划异常处理机制
考虑到在后端处理业务的过程中,会出现各种异常情况,如执行过程中数据库宕机、用户名重复等。
虽然java在异常处理机制已经很完善,以上的情况都是抛出RuntimeException异常,对定位异常不够明确。
因此在业务层的制定中,需要考虑对异常的定义处理。
在业务层制定一个继承RuntimeException异常的异常类,再让具体的异常继承这个异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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); } }
根据业务层不同的 来详细定义具体异常的类型,统一的继承ServiceException异常基类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class UsernameDuplicateException extends ServiceException { public UsernameDuplicateException () { } public UsernameDuplicateException (String message) { super (message); } public UsernameDuplicateException (String message, Throwable cause) { super (message, cause); } public UsernameDuplicateException (Throwable cause) { super (cause); } public UsernameDuplicateException (String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super (message, cause, enableSuppression, writableStackTrace); } } public class InsertException extends ServiceException {}public class ValidCodeNotMatchException extends RuntimeException {}
2.定义业务层接口和抽象方法
1 2 3 4 5 public interface IUserService { void UserRegister (User user) ; }
3.定义接口的实现类,处理请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 @Service public class IUserServiceImpl implements IUserService { @Autowired(required = false) private UserMapper userMapper; @Override public void UserRegister (User user) { User queryUser = userMapper.queryUserByUsername(user.getUsername()); if (queryUser != null ){ throw new UsernameDuplicateException ("用户名已被注册" ); } String oldPassword = user.getPassword(); String salt = UUID.randomUUID().toString().toUpperCase(); user.setSalt(salt); String md5Password = PasswordEncryptedUtils.getPasswordByMD5(oldPassword, salt); user.setPassword(md5Password); user.setIsDelete(0 ); Date currentTime = new Date (); user.setCreatedUser(user.getUsername()); user.setCreatedTime(currentTime); user.setModifiedUser(user.getUsername()); user.setModifiedTime(currentTime); int result = userMapper.addUser(user); if (result == 0 ){ throw new InsertException ("处理用户注册过程中,服务器或数据库执行出现异常" ); } } }
4.业务层进行单元测试
后端-控制层 1.设置返回响应信息给前端的基类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Data public class JsonResult <E> { private int code; private String message; private E data; public JsonResult () { } public JsonResult (int code) { this .code = code; } public JsonResult (Throwable e) { this .message = e.getMessage(); } public JsonResult (int code, E data) { this .code = code; this .data = data; } }
2.设计请求
请求路径:/user
请求参数:User user,HttpSession session,String code
请求类型:post
响应结果:JsonResult< Void>
3.controller的设计
①创建一个BaseController全局处理自定义的异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class BaseController { public static final int OK = 200 ; @ExceptionHandler(ServiceException.class) public JsonResult<Void> handleException (Throwable e) { JsonResult<Void> result = new JsonResult <>(e); if (e instanceof UsernameDuplicateException){ result.setStatus(4000 ); result.setMessage(e.getMessage()); }else if (e instanceof InsertException){ result.setStatus(5000 ); result.setMessage(e.getMessage()); } return result; } }
②创建一个UserController处理请求
UserController继承了BaseControlle也就间接用于了BaseControlle的属性和方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @RestController @RequestMapping("/user") public class UserController extends BaseController { @Autowired private IUserService userService; @PostMapping public JsonResult<Void> userRegister (User user,HttpSession session,String code) { String validCode = (String) session.getAttribute(Constants.KAPTCHA_SESSION_KEY); if (!validCode.equals(code)){ throw new ValidCodeNotMatchException ("验证码错误,请重试!" ); } userService.userRegister(user); return new JsonResult <>(OK); } }
4.使用postman进行接口的测试
前端页面
前端页面只需要将表单信息通过ajax异步向后端服务器发起请求即可
给用户注册按钮绑定点击事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 <script type="text/javascript" > $(function ( ) { $(".footer" ).load ("components/footer.html" ) checkInfoAndSendAjax (); $("#btn-toIndex" ).click (function ( ) { location.href = "index.html" }) }) function checkInfoAndSendAjax ( ) { $("#btn-reg" ).click (function ( ) { let name = $("#username" ).val (); let pwd = $("#password" ).val (); let rePwd = $("#rePwd" ).val (); let codeStr = $("#code" ).val (); codeStr = $.trim (codeStr); if (name == "" || pwd == "" || rePwd == "" ||codeStr == "" ){ $("#error-msg" ).text ("请先填写需要注册的信息!" ); return false ; } let nameCheck = /^\w{5,12}$/ ; let username = $("#username" ).val (); if (!(nameCheck.test (username))){ $("#error-msg" ).text ("用户名必须是5-12位的字母和数字" ); return false ; }else { $("#error-msg" ).empty () } let passCheck = /^\w{5,12}$/ ; let password = $("#password" ).val (); if (!passCheck.test (password)){ $("#error-msg" ).text ("密码必须是5-12位的字母和数字" ); return false ; }else { $("#error-msg" ).empty () } let rePass = $("#rePwd" ).val (); if (rePass !== password){ $("#error-msg" ).text ("密码不一致" ); return false ; }else { $("#error-msg" ).empty () } $.ajax ({ url : "http://localhost:8080/user" , type : "post" , data : $("#form-reg" ).serialize (), dataType : "json" , success : function (res ) { if (res.status === 200 ){ alert ("注册成功!" ) location.href = "login.html" }else { $("#error-msg" ).html (res.message ) } }, error : function (error ) { alert (error.status + "错误,服务器出现故障,请等待攻城狮修复!!" ) } }); }) } </script>
登录 后端-持久层
持久层可以利用上面写好的sql语句判断用户是否存在即可,密码的校验 交由业务层进行处理
1.sql语句用上面的
2.mapper接口用上面的
3.mapper接口的映射文件用上面的
4.单元测试
后端-业务层 1.规划异常,创建两个自定义的异常处理类继承ServiceException
1 2 3 4 public class UserNotExistException extends ServiceException {}public class WrongPasswordException extends ServiceException {}
2.编写接口和抽象方法以及实现类内具体的业务处理流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 User userLogin (User user) ; public User userLogin (User user) { String username = user.getUsername(); String userPassword = user.getPassword(); User loginUser = userMapper.queryUserByUsername(username); if (loginUser == null ){ throw new UserNotExistException ("用户数据不存在" ); } String salt = loginUser.getSalt(); String databasePwd = loginUser.getPassword(); Integer deleteStatus = loginUser.getIsDelete(); String md5PasswordBy = PasswordEncryptedUtils.getPasswordByMD5(userPassword, salt); if (!databasePwd.equals(md5PasswordBy)){ throw new WrongPasswordException ("密码不正确" ); } if (deleteStatus == 1 ){ throw new UserNotExistException ("用户数据不存在" ); } return loginUser; }
3.进行单元测试
后端-控制层 1.在全局异常处理机制中添加对业务层异常的处理
2.设计请求
请求路径:/user
请求参数:User user, HttpSession session,String code
请求类型:get
响应结果:JsonResult< User>
3.处理请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @GetMapping public JsonResult<User> userLogin (User user, HttpSession session) { String validCode = (String) session.getAttribute(Constants.KAPTCHA_SESSION_KEY); if (!validCode.equals(code)){ throw new ValidCodeNotMatchException ("验证码错误,请重试!" ); } User loginUser = userService.userLogin(user); session.setAttribute("uid" ,loginUser.getUid()); session.setAttribute("username" ,loginUser.getUsername()); User newUser = new User (); newUser.setUsername(loginUser.getUsername()); newUser.setUid(loginUser.getUid()); newUser.setGender(loginUser.getGender()); newUser.setPhone(loginUser.getPhone()); newUser.setEmail(loginUser.getEmail()); newUser.setAvatar(loginUser.getAvatar()); return new JsonResult <>(OK,newUser); }
4.控制层单元测试
前端页面 登录成功以及登录成功后需要做的事情:
1 2 sessionStorage.setItem ("user" ,JSON .stringify (res.data ));
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class BaseController { public final Integer getUserIdFromSession (HttpSession session) { String uidStr = session.getAttribute("uid" ).toString(); return Integer.valueOf(uidStr); } public final String getUsernameFromSession (HttpSession session) { return session.getAttribute("username" ).toString(); } }
拦截器 ,对每个访问的页面进行拦截判断,没有登录则重定向至登录页面
①在interceptor包下自定义拦截器类,实现HandleInterceptor接口,实现此接口的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); if (session.getAttribute("uid" ) != null ){ return true ; }else { response.sendRedirect("/web/login.html" ); return false ; } } }
②在config包下创建自定义配置类,实现WebMvcConfigurer接口,将拦截器添加到容器中
指定拦截规则【如果是拦截所有,静态资源也会被拦截,所以要指定白名单和黑名单】
拦截器也要放行接口的请求,不然就报错
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Configuration public class LoginConfig implements WebMvcConfigurer { @Override public void addInterceptors (InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor ()) .addPathPatterns("/**" ) .excludePathPatterns("/web/login.html" ,"/web/index.html" , "/web/register.html" ,"/web/product.html" , "/web/components/**" ,"/web/search.html" , "/user/**" ,"/address/**" ,"/file/**" ,"/district/**" , "/product/**" ,"/cart/**" ,"/order/**" ,"/kaptcha/**" , "/css/**" ,"/bootstrap3/**" , "/images/**" ,"/js/**" ); } }
修改密码 后端-持久层 1.编写sql语句
1 update t_user set password = #{password} where uid = #{uid};
2.定义mapper接口的抽象方法
1 int updatePassword (String password,String uid) ;
3.编写mapper接口的映射文件
1 2 3 4 <update id ="updatePassword" > update t_user set password = #{password} where uid = #{uid}; </update >
4.单元测试
后端-业务层 1.异常处理,创建一个表示密码不匹配的异常,比如原密码不对
2.定义Userservice的抽象方法
3.编写实现类实现接口方法的业务处理逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 public class OriginalPasswordNotMatchException extends ServiceException {}public interface IUserService { void userResetPwd (String oldPwd,String newPwd, HttpSession session) ; } @Override public void userResetPwd (String originalPassword,String newPassword, HttpSession session) { String username = session.getAttribute("username" ).toString(); User user = userMapper.queryUserByUsername(username); String salt = user.getSalt(); String databasePwd = user.getPassword(); String md5Password = PasswordEncryptedUtils.getPasswordByMD5(originalPassword, salt); if (!databasePwd.equals(md5Password)){ throw new OriginalPasswordNotMatchException ("原密码不正确" ); } String newMD5Pwd = PasswordEncryptedUtils.getPasswordByMD5(newPassword, salt); Date currentTime = new Date (); int result = userMapper.updatePassword(newMD5Pwd,user.getUsername(),currentTime,username); if (result == 0 ){ throw new InsertException ("数据库或服务器故障,密码修改失败" ); } }
4.单元测试
后端-控制层 1.在全局异常处理机制中添加对业务层异常的处理
2.设计请求
请求路径 /user
请求参数 String oldPwd,String newPwd,Httpsession session
请求类型 post
响应类型 JsonResult< Void>
3.处理请求
1 2 3 4 5 6 7 @PutMapping public JsonResult<Void> userResetPwd (@RequestParam("oldPassword") String oldPwd, @RequestParam("newPassword") String newPwd, HttpSession session) { userService.userResetPwd(oldPwd, newPwd, session); return new JsonResult <>(OK); }
4.单元测试
前端页面
与上面用户登录大致的逻辑,这里不浪费空间了,代码放在web/password.html页面下
补充一点:在用户修改密码后,利用location.href = “login.html”
以及在后端删除session中存储的uid值实现让用户重新登录
个人资料 后端-持久层 1.编写sql语句
需要更新phone、email、gender、modified_user ,modified_time这五个字段
1 2 3 4 5 6 7 8 9 10 / / 根据uid主键查询用户信息select * from t_user where uid = #{uid}/ / 更新的sql 语句update t_user set phone = #{phone}, email = #{email}, gender = #{gender}, modified_user = #{modifiedUser}, modified_time = #{modifiedTime} where uid = #{uid}
2.定义mapper接口抽象方法
1 2 3 4 5 6 7 8 User queryUserByUid (@Param("uid") Integer uid) ; int UpdateUserInfo (String phone, String email, Integer gender, String modifiedUser, Date modifiedTime, Integer uid) ;
3.编写映射文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <select id ="queryUserByUid" resultMap ="queryUser" > select * from t_user where uid = #{uid} </select > <update id ="UpdateUserInfo" > update t_user set <if test ="phone != null and phone != ''" > phone = #{phone}, </if > <if test ="email != null and email != ''" > email = #{email}, </if > <if test ="gender != null and gender != ''" > gender = #{gender}, </if > modified_user = #{modifiedUser}, modified_time = #{modifiedTime} where uid = #{uid} </update >
4.单元测试
后端-业务层 1.异常控制,好像没什么新异常
2.定义service层接口的抽象方法
3.实现抽象方法,编写业务处理逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 void userUpdateInfo (String phone,String email, Integer gender,String username, Integer uid) ;User queryUserByUid (Integer uid) ; @Override public User userUpdateInfo (String phone, String email, Integer gender,String username,Integer uid) { User user = userMapper.queryUserByUid(uid); if (user == null | user.getIsDelete() == 1 ){ throw new UserNotExistException ("用户数据不存在" ); } int result = userMapper.UpdateUserInfo(phone, email, gender, username, new Date (), uid); if (result == 0 ){ throw new InsertException ("数据库或服务器异常,个人资料修改失败" ); } }
4.单元测试
后端-控制层 1.无异常,不需要处理
2.设计请求
请求路径 /user/updateInfo
请求参数 String phone,String email,Integer gender,HttpSession session
请求类型 post
响应类型 JsonResult< User>
3.处理请求
考虑到前端信息及时更新的问题,因此这里选择将更新后的User进行回传
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @GetMapping("/queryUser") public JsonResult<User> queryUserByUid (HttpSession session) { Integer uid = getUserIdFromSession(session); User user = userService.queryUserByUid(uid); User newUser = new User (); newUser.setUsername(user.getUsername()); newUser.setUid(user.getUid()); newUser.setGender(user.getGender()); newUser.setPhone(user.getPhone()); newUser.setEmail(user.getEmail()); newUser.setAvatar(user.getAvatar()); return new JsonResult <>(OK,newUser); } @PutMapping("/updateInfo") public JsonResult<User> userInfoUpdate (String phone,String email,Integer gender,HttpSession session) { String username = getUsernameFromSession(session); Integer id = getUserIdFromSession(session); User user = userService.userUpdateInfo(phone, email, gender, username, id); User newUser = new User (); newUser.setUsername(user.getUsername()); newUser.setUid(user.getUid()); newUser.setGender(user.getGender()); newUser.setPhone(user.getPhone()); newUser.setEmail(user.getEmail()); return new JsonResult <>(OK,newUser); }
前端页面 ①第一个ajax请求在用户信息页面加载完成后自动发送,并根据返回值通过js的id选择器
找到对应的元素并修改其属性值
②第二个ajax请求在用户点击修改按钮之后先提示是否修改,再根据其选择进行处理,
同理根据结果利用js的id选择器找到对应的元素并修改其属性值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 <!-- js代码 --> <script type ="text/javascript" > $(function ( ) { $.ajax ({ url : "http://localhost:8080/user/queryUser" , type : "get" , dataType : "json" , success : function (res ) { if (res.status === 200 ){ $("#username" ).val (res.data .username ) $("#phone" ).val (res.data .phone ) $("#email" ).val (res.data .email ) if (res.data .gender === 0 ){ $("#gender-female" ).prop ("checked" ,"checked" ) }else { $("#gender-male" ).prop ("checked" ,"checked" ) } } }, error : function (error ) { alert ("服务器出现故障,请等待攻城狮修复!!" ) } }); $("#btn-change-info" ).click (function ( ) { if (confirm ("确定要修改吗?" )){ $.ajax ({ url : "http://localhost:8080/user/updateInfo" , type : "put" , data : $("#form-change-info" ).serialize (), dataType : "json" , success : function (res ) { if (res.status === 200 ){ alert ("修改成功!" ) sessionStorage.setItem ("user" ,JSON .stringify (res.data )) location.reload (); }else { alert (res.message ) } }, error : function (error ) { alert ("服务器出现故障,请等待攻城狮修复!!" ) } }); } else { return false ; } }) }) </script >
头像上传 后端-持久层 1.编写sql语句
对应的avatar字段存其保存地址
1 2 update t_user set avatar = #{avatar},modified_user = #{modifiedUser}, modified_time = #{modifiedTime} where uid = #{uid};
2.定义mapper接口的抽象方法
1 2 3 4 int updateUserAvatar (@Param("file") String ImgAddress, String modifiedUser, Date modifiedTime, Integer uid) ;
3.编写mapper接口的映射文件
1 2 3 4 <update id ="updateUserAvatar" > update t_user set avatar = #{avatar} where uid = #{uid}; </update >
4.单元测试
后端-业务层 1.异常规划 例如用户数据不存在,服务器宕机等
2.定义service层接口抽象方法
3.实现类重写抽象方法,编写业务处理逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void userUploadImg (String imgAddress,Integer uid) ;@Override public void userUploadImg (String imgAddress, Integer uid) { User user = userMapper.queryUserByUid(uid); if (user == null ){ throw new UserNotExistException ("用户数据不存在" ); } int result = userMapper.updateUserAvatar(imgAddress, user.getUsername(),new Date (),uid); if (result == 0 ){ throw new InsertException ("图片在服务器或数据库更新过程中出现错误,上传失败!" ); } }
4.单元测试
后端-控制层 1.处理控制层和业务层异常,将控制层异常加入全局异常处理@ExceptionHandler的值中
2.设计请求
请求路径:/file
请求参数:MultipartFile file,Httpsession session
请求类型:post
响应类型:JsonResult< Void>
3.处理请求
①创建一个Controller专门处理文件的上传和下载
②将文件的下载地址保存到数据库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 @RestController @RequestMapping("/file") public class FileController extends BaseController { @Autowired private IUserService userService; @Value("${server.port}") private String port; @Value("${spring.servlet.multipart.max-file-size}") private Integer fileMaxSize; private static final List<String> FILE_TYPE = new ArrayList <>(); static { FILE_TYPE.add("images/jpg" ); FILE_TYPE.add("images/jpeg" ); FILE_TYPE.add("images/png" ); FILE_TYPE.add("images/bmp" ); FILE_TYPE.add("images/gif" ); } @PostMapping public JsonResult<Void> userAvatarUpload (MultipartFile file, HttpSession session) { if (file.isEmpty()){ throw new FileEmptyException ("上传文件为空,上传失败" ); } if (file.getSize() > fileMaxSize){ throw new FileSizeException ("文件过大,上传失败" ); } if (!FILE_TYPE.contains(file.getContentType())){ throw new FileTypeNotMatchException ("文件类型不符,上传失败" ); } String originalFilename = file.getOriginalFilename(); String suffix = originalFilename.substring(originalFilename.lastIndexOf("." )); String uuidName = UUID.randomUUID().toString(); String fileName = uuidName + suffix; String realPath = System.getProperty("user.dir" ) + "/src/main/resources/static/images/img/" + fileName; File destFile = new File (realPath); File parentFile = destFile.getParentFile(); if (!parentFile.exists()){ parentFile.mkdirs(); } try { file.transferTo(destFile); }catch (FileStatusException e) { throw new FileStatusException ("文件状态异常,写入失败" ); }catch (IOException e) { throw new FileUploadIOException ("服务器或数据库写入文件失败" ); } Integer uid = getUserIdFromSession(session); String fileAccessPath = "http://localhost:" + port + "/file/down/" + fileName; userService.userUploadImg(fileAccessPath,uid); return new JsonResult <>(OK); } @GetMapping("/down/{name}") public ResponseEntity<byte []> fileUpload(@PathVariable("name") String fileName) throws IOException { String downFilePath = System.getProperty("user.dir" ) + "/src/main/resources/static/images/img/" + fileName; if (downFilePath != null ){ FileInputStream inputStream = new FileInputStream (new File (downFilePath)); byte [] fileBytes = new byte [inputStream.available()]; inputStream.read(fileBytes); HttpHeaders headers = new HttpHeaders (); headers.add("Content-Disposition" , "attachment;filename=" + URLEncoder.encode(fileName,"UTF-8" )); HttpStatus statusCode = HttpStatus.OK; ResponseEntity<byte []> responseEntity = new ResponseEntity <>(fileBytes, headers, statusCode); inputStream.close(); return responseEntity; } return null ; } }
4.单元测试
前端页面 ①在页面加载完成之前自动发送第一个ajax请求。
②这里使用form表单提交图像,但form表单提交会自动跳转,因此需要阻止submit的跳转行为
引入js文件,在form表单添加onsubmit属性以及利用js插件解决
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <!-- 引入阻止表单上传跳转的js--> <script src ="../js/jquery-1.8.0.min.js" > </script > <script src ="../js/jquery.form.js" > </script > <!--js代码 --> <script type ="text/javascript" > function afterFromSubmit ( ){ $("#avatar" ).ajaxSubmit (function ( ) { alert ("上传成功" ) location.href = "upload.html" }) return false ; } $(function ( ) { $.ajax ({ url : "http://localhost:8080/user/queryUser" , type : "get" , dataType : "json" , success :function (res ) { if (res.data .avatar !== null &&res.data .avatar !== "" ){ $("#img-avatar" ).attr ("src" ,res.data .avatar ) }else { $("#img-avatar" ).attr ("src" ,"../images/index/user.jpg" ) } }, }) }) </script >
地址管理
创建数据表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 CREATE TABLE t_address ( aid INT AUTO_INCREMENT COMMENT '收货地址id' , uid INT COMMENT '归属的用户id' , NAME VARCHAR (20 ) COMMENT '收货人姓名' , province_name VARCHAR (15 ) COMMENT '省-名称' , province_code CHAR (6 ) COMMENT '省-行政代号' , city_name VARCHAR (15 ) COMMENT '市-名称' , city_code CHAR (6 ) COMMENT '市-行政代号' , area_name VARCHAR (15 ) COMMENT '区-名称' , area_code CHAR (6 ) COMMENT '区-行政代号' , zip CHAR (6 ) COMMENT '邮政编码' , address VARCHAR (50 ) COMMENT '详细地址' , phone VARCHAR (20 ) COMMENT '手机' , tel VARCHAR (20 ) COMMENT '固话' , tag VARCHAR (6 ) COMMENT '标签' , is_default INT COMMENT '是否默认:0-不默认,1-默认' , created_user VARCHAR (20 ) COMMENT '创建人' , created_time DATETIME COMMENT '创建时间' , modified_user VARCHAR (20 ) COMMENT '修改人' , modified_time DATETIME COMMENT '修改时间' , PRIMARY KEY (aid) ) ENGINE= INNODB DEFAULT CHARSET= utf8;
创建实体类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Data @NoArgsConstructor @AllArgsConstructor public class Address extends BaseEntity { private Integer aid; private Integer uid; private String name; private String provinceName; private String provinceCode; private String cityName; private String cityCode; private String areaName; private String areaCode; private String zip; private String address; private String phone ; private String tel; private String tag; private Integer isDefault; }
新增地址 后端-持久层 1.编写sql语句
1 2 3 4 5 6 7 8 9 10 11 12 / / 插入数据 除了此表的主键不需要数据,其他都要insert into t_address(uid,name,province_name,province_code,city_name,city_code,area_name, area_code,zip,address,phone,tel,tag,is_default,created_user,created_time, modified_user,modified_time) values (#{uid},#{name},#{proviceName},#{provinceCode},#{cityName}, #{cityCode},#{areaName},#{areaCode},#{zip},#{address},#{phone}, #{tel},#{tag},#{isDefault},#{createdUser},#{createdTime}, #{modifiedUser},#{modifiedTime} ) / / 判断用户数据条目是否超过限制select count (* ) from t_address where uid = #{uid}
2.定义抽象接口和抽象方法
1 2 3 4 5 public interface AddressMapper { void addAddress (Address address) ; int userAddressCount (Integer aid) ; }
3.编写mapper接口对应的映射文件
4.单元测试
后端-业务层 1.异常规划 用户地址条目数超出限制的异常
2.定义service接口和抽象方法
1 2 3 4 public interface IAddressService { void addAddress (Address address) ; }
3.编写具体的业务处理逻辑
需要注意,如果用户此时添加的地址是第一条,需要将其设置为其账户的默认地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Service public class IAddressServiceImpl implements IAddressService { @Autowired private AddressMapper addressMapper; @Override public void addAddress (Address address) { int count = addressMapper.userAddressCount(address.getUid()); if (count >= 20 ){ throw new AddressCountLimitException ("地址数量已达上限,请先删除部分地址!" ); } if (count == 0 ){ address.setIsDefault(1 ); } int result = addressMapper.addAddress(address); if (result == 0 ){ throw new InsertException ("新增地址失败,服务器或数据库异常!" ); } } }
4.单元测试
后端-控制层 1.处理异常,将异常添加到全局统一管理
2.设计请求
请求路径:/address
请求参数:Address address,HttpSession session
请求类型:post
响应类型:JsonResult< Void>
3.处理请求,创建对应的controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @RestController @RequestMapping("/address") public class AddressController extends BaseController { @Autowired private IAddressService addressService; @PostMapping public JsonResult<Void> addAddress (Address address, HttpSession session) { Integer uid = getUserIdFromSession(session); address.setUid(uid); addressService.addAddress(address); return new JsonResult <>(OK); } }
前端页面 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <script type="text/javascript" > $(function ( ) { $("#btn-add-new-address" ).click (function ( ) { $.ajax ({ url : "http://localhost:8080/address" , type : "post" , data : $("#form-add-new-address" ).serialize (), dataType : "json" , success : function (res ) { if (res.status === 200 ){ alert ("新增成功!" ) location.href = "address.html" }else { alert (res.message ) } }, error : function ( ) { alert ("服务器出现故障,请等待攻城狮修复!!" ) } }) }) }) </script>
获取省市区列表 创建数据表 1 2 3 4 5 6 7 8 / / parent属性代表的是父区域的代码号,省的父代码号是+ 86 CREATE TABLE t_dict_district ( id int (11 ) NOT NULL AUTO_INCREMENT, parent varchar (6 ) DEFAULT NULL , code varchar (6 ) DEFAULT NULL , name varchar (16 ) DEFAULT NULL , PRIMARY KEY (id) ) ENGINE= InnoDB DEFAULT CHARSET= utf8;
创建实体类 1 2 3 4 5 6 7 @Data public class District { private Integer id; private String parent; private String code; private String name; }
后端-持久层 1.编写sql语句
1 2 3 4 5 / / 根据父代号查询相关信息select * from t_dict_district where parent = #{parent} order by code ASC / / 根据code查询对应的省市区名字select name from t_dict_district where code = #{code}
2.定义mapper接口和抽象方法
1 2 3 4 List<District> queryDistrictByParent (String parent) ; String queryDistrictByCode (String code) ;
3.编写映射文件
4.单元测试
后端-业务层 1.规划异常 ,暂无异常
2.定义service接口和抽象方法
3.编写具体的业务处理逻辑
此外,还需要在address业务层需要调用District业务层为它的address对象填充空白字段
因为provinceName、cityName、areaName的值需要通过前端传过来的option标签的
value值(这些option标签是通过这个业务层查询后append到select标签中的)进行查询
才能得到,因此还需要在address业务层调用这个业务层接口
select标签中的option标签的value的值是前端往后端传的参数
而在这之前添加关于地址的字段全空,code为名字就是因为select中没有选项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Service public class IDistrictServiceImpl implements IDistrictService { @Autowired private DistrictMapper districtMapper; @Override public List<District> getSpecifyDistrictByParent (String parent) { List<District> districts = districtMapper.queryDistrictByParent(parent); for (District ad: districts) { ad.setId(null ); ad.setParent(null ); } return districts; } @Override public String getNameByCode (String code) { return districtMapper.queryDistrictByCode(code); } }
4.单元测试
后端-控制层 1.无异常,不用处理
2.设计请求
请求路径:/district
请求参数:String parent
请求类型:get
响应类型:JsonResult<List< District>>
3.处理请求,创建一个新的控制类
1 2 3 4 5 6 7 8 @GetMapping public JsonResult<List<District>> queryDistrictByParent (String parent) { List<District> list = districtService.getSpecifyDistrictByParent(parent); return new JsonResult <>(OK,list); }
4.单元测试
前端页面
前端页面js代码过长,需要到/web/addAddress.html的script标签下看
获取用户地址 后端-持久层 1.编写sql语句
1 select * from t_address where uid = #{uid} order by is_default desc ,created_time desc
2.定义抽象方法
1 2 List<Address> queryUserAddress (Integer uid) ;
3.编写映射文件
定义结果结映射规则,将通过select查询出来的结果放入List当中
4.单元测试
后端-业务层 1.异常控制,貌似没新异常
2.定义业务层抽象方法
3.编写具体的业务处理逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 List<Address> queryUser (Integer uid) ; @Override public List<Address> queryUser (Integer uid) { User user = userService.queryUserByUid(uid); if (user == null | user.getIsDelete() == 1 ){ throw new UserNotExistException ("用户信息不存在" ); } return addressMapper.queryUserAddress(uid); }
4.单元测试
后端-控制层 1.异常处理
2.设计请求
请求地址:/address
请求参数:HttpSession session
请求类型:get
响应类型:JsonResult<List< Address>>
3.处理请求
控制层需要把前端发送过来的Address其余空字段补全
1 2 3 4 5 6 7 8 9 10 @GetMapping public JsonResult<List> queryAllAddress (HttpSession session) { Integer uid = getUserIdFromSession(session); List<Address> list = addressService.queryUser(uid); return new JsonResult <>(OK,list); }
4.单元测试
前端页面
在页面加载完成时,自动发送ajax请求查询用户收货地址,拿到返回数据之后
判断返回数据的j集合中是否有信息,没有信息将表格内容设置为提示信息,
有内容则通过js选择器找到显示表格的标签,通过append()将数据逐个插入到表格的标签内
并将修改、删除、设为默认的三个 绑定单击事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 <script type="text/javascript" > $(function ( ) { $.ajax ({ url : "http://localhost:8080/address" , type : "get" , success : function (res ) { if (res.data .length !== 0 ){ for (i = 0 ;i < res.data .length ;i++){ var address = res.data [i]; str = "<tr>" + "<td>" + address.tag + "</td>" + "<td>" + address.name + "</td>" + "<td>" + address.provinceCode + address.cityCode + address.areaCode + address.address + "</td>" + "<td>" + address.phone + "</td>" + "<td>" + "<a href='javascript:void(0);' onclick='updateAddress()' class='btn btn-xs btn-info'>" + "<span class='fa fa-edit'></span>修改" + "</a>" + "</td>" + "<td>" + "<a href='javascript:void(0);' onclick='deleteAddress()' class='btn btn-xs add-del btn-info'>" + "<span class='fa fa-trash-o'></span>删除" + "</a>" + "</td>" + "<td>" + "<a href='javascript:void(0);' onclick='setDefault()' class='btn btn-xs add-def btn-default'>设为默认</a>" + "</td>" + "</tr>" $("#address-list" ).append (str) } }else { var text = "暂无收货地址,请先添加收货地址" $("#address-list" ).text (text) } } }) }) </script>
设置默认地址 后端-持久层 1.编写sql语句
1 2 3 4 5 6 7 8 9 10 11 / / 判断需要设置为默认地址的数据是否存在select * from t_address where aid = #{aid}/ / 将所有地址设置为非默认值update t_address set is_default = 0 where uid = #{uid} and is_delete = 0 / / 更新地址的默认值update t_address set is_default = 1 , modified_user = #{modifiedUser}, modified_time = #{modifiedTime} where aid = #{aid}
2.定义抽象方法
1 2 3 4 5 6 Address queryUserAddressByAid (Integer aid) ; int setAllAddressNotDefault (Integer uid) ;int setOneAddressDefault (Integer aid,String modifiedUser, Date modifiedTime) ;
3.编写mapper映射文件
后端-业务层 1.异常规划,数据不存在
2.定义service层抽象方法
3.编写具体的业务处理逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 Address queryAddressByAid (Integer aid) ; int setNotDefaultAddress (Integer uid) ;int setOneAddressDefault (Integer aid,String modifiedUser, Date modifiedTime) ;@Override public int queryAddressByAid (Integer aid) { return addressMapper.queryUserAddressByAid(aid); } @Override public int setNotDefaultAddress (Integer uid) { return addressMapper.setAllAddressNotDefault(uid); } @Override public int setOneAddressDefault (Integer aid,String modifiedUser, Date modifiedTime) { return addressMapper.setOneAddressDefault(aid,modifiedUser,modifiedTime); }
后端-控制层 1.将异常加入全局处理
2.设计请求
请求路径:/address/setAddress
请求参数:Integer aid,HttpSession session
请求类型:post
响应类型:JsonResult< void>
3.处理请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @PostMapping("/setAddress") public JsonResult<Void> setUserDefaultAddress (Integer aid,HttpSession session) { Address address = addressService.queryAddressByAid(aid); if (address == null ){ throw new AddressNotExistsException ("该地址不存在,设置失败" ); } Integer uid = getUserIdFromSession(session); String modifiedUser = getUsernameFromSession(session); Date date = new Date (); addressService.setNotDefaultAddress(uid); addressService.setOneAddressDefault(aid,modifiedUser,date); return new JsonResult <>(OK); }
4.单元测试
前端页面
由于是直接通过拼接元素实现了数据展示,对于id的获取选择使用正则表达式进行替换
使用正则表达式替换获取该地址的aid值,#{aid}只是一个占位符的含义,没其他含义
str = str.replace(“#{aid}”,address.aid)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function setDefault (aid ){ alert (aid) if (confirm ("确定要这条收货地址设为默认地址吗?" )){ $.ajax ({ url : "http://localhost:8080/address/setAddress" , type : "post" , data : "aid=" + aid, dataType : "json" , success :function (res ) { alert ("设置成功" ) location.reload (); }, error :function (error ) { alert (error.message ) } }) } }
删除地址 后端-持久层 1.sql语句的编写
1 2 3 4 update t_address set is_delete = 1 , modified_user = #{modifiedUser}, modified_time = #{modifiedTime} where aid = #{aid})
2.定义mapper抽象方法
1 int deleteAddressByAid (Integer aid,String modifiedUser, Date modifiedTime) ;
3.编写mapper映射文件
4.单元测试
后端-业务层 1.异常控制,貌似没有
2.定义抽象方法
3.编写具体的处理逻辑
4.单元测试
后端-控制层 1.异常处理,貌似没有
2.设计请求
3.处理请求
4.单元测试
前端页面
js代码与上方的设置默认地址相似,就不浪费空间了
修改地址 后端-持久层 1.sql语句的编写
1 update t_address set (除了uid、创建者和创建时间的字段) where aid = #{aid} and is_delete = 0
2.定义mapper抽象方法
1 2 int updateUserAddressByAid (Address address) ;
3.编写mapper映射文件
4.单元测试
后端-业务层 1.异常控制,貌似没有
2.定义抽象方法
3.编写具体的处理逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int updateOneAddress (Address address,String modifiedUser) ;@Override public int updateOneAddress (Address address,String modifiedUser) { String provinceName = districtService.getNameByCode(address.getProvinceCode()); String cityName = districtService.getNameByCode(address.getCityCode()); String areaName = districtService.getNameByCode(address.getAreaCode()); address.setProvinceName(provinceName); address.setCityName(cityName); address.setAreaName(areaName); address.setModifiedUser(modifiedUser); address.setModifiedTime(new Date ()); int result = addressMapper.updateUserAddressByAid(address); return result; }
4.单元测试
后端-控制层 1.异常处理,貌似没有
2.设计请求
请求地址:/address/updateAddress
请求参数:Address address,HttpSession session
请求类型:post
响应类型:JsonResult< void>
3.处理请求
1 2 3 4 5 6 7 8 9 10 @PostMapping("/updateAddress") public JsonResult<Void> updateOneAddress (Address address,HttpSession session) { String modifiedUser = getUsernameFromSession(session); int result = addressService.updateOneAddress(address, modifiedUser); if (result == 0 ){ throw new InsertException ("修改地址时,服务器或数据库异常" ); } return new JsonResult <>(OK); }
4.单元测试
前端页面 ①在跳转到这个页面时,自动发送一个ajax请求,根据返回数据填充表单
②在用户修改省份时,城市、市县自动变为请选择城市或区县并根据选择发送对应的ajax请求查询
③在页面加载完成时,用户可根据自由选择城市或区县的单个修改
④修改按钮前端将整个表单以ajax请求进行发送,后端以实体类address接收并处理
⑤恢复原地址信息按钮实际上就是再次调用第一个中的ajax请求
javaScript代码长达200多行,不予展示,需要到项目于的/web/editAddress.html页面查看
商品管理 创建数据表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 CREATE TABLE t_product ( id int (20 ) NOT NULL COMMENT '商品id' , category_id int (20 ) DEFAULT NULL COMMENT '分类id' , item_type varchar (100 ) DEFAULT NULL COMMENT '商品系列' , title varchar (100 ) DEFAULT NULL COMMENT '商品标题' , sell_point varchar (150 ) DEFAULT NULL COMMENT '商品卖点' , price bigint (20 ) DEFAULT NULL COMMENT '商品单价' , num int (10 ) DEFAULT NULL COMMENT '库存数量' , image varchar (500 ) DEFAULT NULL COMMENT '图片路径' , status int (1 ) DEFAULT '1' COMMENT '商品状态 1:上架 2:下架 3:删除' , priority int (10 ) DEFAULT NULL COMMENT '显示优先级' , created_time datetime DEFAULT NULL COMMENT '创建时间' , modified_time datetime DEFAULT NULL COMMENT '最后修改时间' , created_user varchar (50 ) DEFAULT NULL COMMENT '创建人' , modified_user varchar (50 ) DEFAULT NULL COMMENT '最后修改人' , PRIMARY KEY (id) ) ENGINE= InnoDB DEFAULT CHARSET= utf8;
创建实体类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Data @AllArgsConstructor @NoArgsConstructor public class Product extends BaseEntity { private Integer id; private Integer categoryId ; private String itemType; private String title; private String sellPoint; private Long price; private Integer num; private String image; private Integer status; private String priority; }
热销排行 后端-持久层 1.sql语句编写
1 SELECT id,title,price,image FROM t_product where status = 1 ORDER BY priority DESC LIMIT 0 ,4
2.定义mapper接口和抽象方法
1 2 List<Product> queryPriorityProduct () ;
3.编写mapper映射文件
4.单元测试
后端-业务层 1.规划异常
2.定义service接口和抽象方法
3.编写具体的处理逻辑
1 2 3 4 5 6 7 List<Product> queryPriorityProduct () ; @Override public List<Product> queryPriorityProduct () { return productMapper.queryPriorityProduct(); }
4.单元测试
后端-控制层 1.异常处理,无异常不需要处理
2.设计请求
请求路径:/product
请求参数:无
请求类型:get
响应类型:JsonResult<List< Product>>
3.处理请求
1 2 3 4 5 6 7 @GetMapping public JsonResult<List<Product>> queryBestProduct () { List<Product> products = productService.queryPriorityProduct(); return new JsonResult <>(OK,products); }
4.单元测试
前端页面 ①在页面加载完成自动发送ajax请求,根据返回信息填充热销排行信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 function showHotProducts ( ){ $.ajax ({ url : "http://localhost:8080/product" , type : "get" , dataType : "json" , success : function (res ) { for (let i = 0 ; i < res.data .length ; i++) { let str = "" ; let product = res.data [i] let image = "http://localhost:8080" + product.image + "collect.png" str = "<div class='col-md-12'>" + "<div class=\"col-md-7 text-row-2\">" + "<a href='#'>" + product.title + "</a>" + "</div>" + "<div class=\"col-md-2\">¥23</div>" + "<div class=\"col-md-3\">" + "<img src=" + image + " class='img-responsive' />" + "</div>" + "</div>" $("#hot-list" ).append (str) } }, error : function ( ) { alert ("查询错误,请等待攻城狮修复!!" ) } }) }
最新产品
所有的逻辑跟热销排行 相似在此不再浪费空间书写,只书写部分
1 2 3 4 5 6 / / sql 语句SELECT id,title,price,image FROM t_product WHERE category_id = 163 and status = 1 ORDER BY created_time DESC LIMIT 0 ,4 ;
显示商品 后端-持久层 1.sql语句的编写
1 select title,sell_point,price,image from t_product where id = #{id}
2.定义抽象方法
1 2 Product queryProductById (Integer id) ;
3.编写mapper映射文件
4.单元测试
后端-业务层 1.规划异常,商品不存在,商品状态异常
2.定义抽象方法
3.编写具体的逻辑处理方法
4.单元测试
后端-控制层 1.处理异常,将异常加入全局处理
2.设计请求
请求路径:/product/{id}
请求参数:Integet id
请求类型:get
响应类型:JsonResult< Product>
3.处理请求
1 2 3 4 5 6 @GetMapping("/{id}") public JsonResult<Product> queryProductById (@PathVariable("id") Integer id) { Product product = productService.queryProductById(id); return new JsonResult <>(OK,product); }
前端页面 ①在页面加载完成自动发送ajax请求,把根据id查询的返回信息填充至页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <script type="text/javascript" > function showInThisProductHtml ( ){ var hrefUrl = location.href ; var param = hrefUrl.split ("=" ) var id = decodeURI (param[1 ]); $.ajax ({ url : "http://localhost:8080/product/" + id, type : "get" , dataType : "json" , success :function (res ) { let product = res.data ; $("#product-title" ).text (product.title ) $("#product-sell-point" ).append (product.sellPoint ) $("#product-price" ).text (product.price ) $("#stock" ).text (product.num ) let image = ".." + product.image for (let i = 1 ; i <= 5 ; i++) { $("#product-image-" + i + "-big" ).attr ("src" ,image + i + "_big.png" ) $("#product-image-" + i).attr ("src" ,image + i + ".jpg" ) } } }) } $(function ( ) { showInThisProductHtml (); }) </script>
立即购买
后端-持久层 、业务层、控制层大部分东西都在前面的 已经实现了
1.根据uid查询用户地址,在地址管理的获取收货地址 已经实现
2.根据pid查询商品信息,在显示商品 已经实现
业务层有所修改,添加一个新的方法对应从商品详情页进去确定订单界面的请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public int insertOrderItemFromProductHtml (Integer oid,Integer pid, Integer num, String username) { Product product = productService.queryProductById(pid); OrderItem orderItem = new OrderItem (); orderItem.setOid(oid); orderItem.setPid(pid); orderItem.setTitle(product.getTitle()); orderItem.setImage(product.getImage()); orderItem.setPrice(product.getPrice()); orderItem.setNum(num); Date createdTime = new Date (); orderItem.setCreatedUser(username); orderItem.setCreatedTime(createdTime); orderItem.setModifiedUser(username); orderItem.setModifiedTime(createdTime); int result = orderMapper.insertOneOrderItem(orderItem); if (result == 0 ){ throw new InsertException ("服务器出现错误,创建订单失败" ); } return result; }
前端页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 $("#btn-create-order" ).click (function ( ) { let aid = getSelectOption (); let totalPrice = JSON .parse ($("#all-price" ).html ()); $.ajax ({ url : "http://localhost:8080/order/createOrder" , type : "post" , data : {aid :aid,totalPrice :totalPrice}, dataType : "json" , success :function (res ) { let oid = res.data .oid let urlParam = location.search .substr (1 ) if (urlParam.search ("cids" ) !== -1 ){ useCidSentAjaxCreateOrderItem (oid); }else { fromProductSentAjaxCreateOrderItem (oid); } }, error : function (error ){ alert (error.message ) } }) }) function fromProductSentAjaxCreateOrderItem (oid ){ $.ajax ({ url : "http://localhost:8080/order/createOrderItem" , type : "post" , data : {oid :oid,pid :pid,num :num}, dataType : "json" , success :function (res ) { if (res.status === 200 ){ location.href = "payment.html?oid=" + oid }else { alert ("创建orderItem订单失败" ) } }, error : function (error ){ alert ("服务器内部异常!" ) } }) }
购物车管理 创建数据表 1 2 3 4 5 6 7 8 9 10 11 12 CREATE TABLE t_cart ( cid INT AUTO_INCREMENT COMMENT '购物车数据id' , uid INT NOT NULL COMMENT '用户id' , pid INT NOT NULL COMMENT '商品id' , price BIGINT COMMENT '加入时商品单价' , num INT COMMENT '商品数量' , created_user VARCHAR (20 ) COMMENT '创建人' , created_time DATETIME COMMENT '创建时间' , modified_user VARCHAR (20 ) COMMENT '修改人' , modified_time DATETIME COMMENT '修改时间' , PRIMARY KEY (cid) ) ENGINE= InnoDB DEFAULT CHARSET= utf8;
创建实体类 1 2 3 4 5 6 7 8 9 10 @Data @AllArgsConstructor @NoArgsConstructor public class Cart extends BaseEntity { private Integer cid; private Integer uid; private Integer pid ; private Long price; private Integer num; }
加入购物车 后端-持久层 1.编写sql语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 / / 根据用户uid和商品pid查询某条购物车cid数据select * from t_cart where uid = #{uid} and pid = #{pid}/ / 更新cid数据update t_cart set price = #{price},num = #{num}, modified_user = #{modifiedUser},modified_time = #{modifiedTime} where cid = #{cid} / / 插入新的cart的数据insert into t_cart(uid,pid,price,num,created_user,created_time,modified_user,modified_time) values (#{uid},#{pid},#{price},#{num}, #{createdUser},#{createdTime}, #{modifiedUser},#{modifiedTime} )
2.定义mapper接口和抽象方法
1 2 3 4 5 6 7 8 9 public interface CartMapper {Cart queryCartByUidAndPid (Integer uid, @Param("pid") Integer pid) ; int updateCartInfo (Cart cart) ;int addCart (Cart cart) ;}
3.编写mapper映射文件
4.单元测试
后端-业务层 1.规划异常。没想到异常
2.定义service接口和抽象方法
3.编写具体逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 Cart queryCartByUidAndPid (Integer uid,Integer pid) ; int UpdateCartInfo (Cart cart,String modifiedUser, Date modifiedTime) ;int addCart (Cart cart,String createdUser,Date createdTime,String modifiedUser, Date modifiedTime) ;public class ICartServiceImpl implements ICartService { @Autowired private CartMapper cartMapper; @Override public Cart queryCartByUidAndPid (Integer uid, Integer pid) { return cartMapper.queryCartByUidAndPid(uid,pid); } @Override public int UpdateCartInfo (Cart cart, String modifiedUser, Date modifiedTime) { cart.setModifiedUser(modifiedUser); cart.setModifiedTime(modifiedTime); return cartMapper.updateCartInfo(cart); } @Override public int addCart (Cart cart,String createdUser,Date createdTime,String modifiedUser, Date modifiedTime) { Integer pid = cart.getPid(); Integer uid = cart.getUid(); Cart destCart = cartMapper.queryCartByUidAndPid(uid, pid); if (destCart == null ){ cart.setCreatedUser(createdUser); cart.setCreatedTime(createdTime); cart.setModifiedUser(modifiedUser); cart.setModifiedTime(modifiedTime); return cartMapper.addCart(cart); }else { Integer destNum = destCart.getNum(); Integer cartNum = cart.getNum(); Integer endNum = destNum + cartNum; destCart.setNum(endNum); destCart.setModifiedUser(modifiedUser); destCart.setModifiedTime(modifiedTime); return cartMapper.updateCartInfo(destCart); } } }
4.单元测试
后端-控制层 1.无异常,不需要处理
2.设计请求
请求地址:/cart/addCart
请求参数:Cart cart, HttpSession session
请求类型:post
响应类型:JsonResult< void>
3.处理请求
1 2 3 4 5 6 7 8 9 10 11 12 13 @PostMapping("/addCart") public JsonResult<Void> addCart (Cart cart, HttpSession session) { Integer uid = getUserIdFromSession(session); String username = getUsernameFromSession(session); cart.setUid(uid); Date date = new Date (); int result = cartService.addCart(cart, username, date, username, date); if (result == 0 ){ throw new InsertException ("服务器或数据库异常,加入购物车失败" ); } return new JsonResult <>(OK); }
前端页面 ①给加入购物车绑定点击事件,点击就发送ajax请求,成功则提示添加成功,在购物车等您结算!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function addProductToCart ( ){ let pid = getPidFromLastHtml (); $(".go-cart" ).click (function ( ) { let price = $("#product-price" ).text () let num = $("#num" ).val () $.ajax ({ url : "http://localhost:8080/cart/addCart" , type : "post" , data : {pid :pid,price :price,num :num}, dataType : "json" , success : function (res ) { alert ("已成功加入购物车,在购物车等您结算哟!" ) }, error : function (err ) { alert ("服务器出现错误,加入购物车失败!" ) } }) }) }
展示购物车 后端-持久层 1.编写sql语句
1 2 3 4 5 6 / / 根据用户uid查询信息SELECT c.cid,c.pid,c.`uid`,c.price,c.num,p.title,p.image,p.`price` AS real_price FROM t_cart c LEFT JOIN t_product p ON c.pid = p.`id` WHERE c.`uid` = #{uid} order by c.created_time desc
2.在对应的mapper接口定义抽象方法
1 2 List<CartVo> queryAllCartsByUid (Integer uid) ;
3.编写mapper的映射文件
4.单元测试
后端-业务层 1.规划异常
2.定义抽象方法
3.编写具体业务处理逻辑
1 2 3 4 5 6 7 List<CartVo> queryCartByUid (Integer uid) ; @Override public List<CartVo> queryCartByUid (Integer uid) { return cartMapper.queryAllCartsByUid(uid); }
4.单元测试
后端-控制层 1.无异常,不需要处理
2.设计请求
请求地址:/cart/showCarts
请求参数:HttpSession session
请求类型:get
响应类型:JsonResult< List< Cart>>
3.处理请求
1 2 3 4 5 6 7 @GetMapping("/showCarts") public JsonResult<List<CartVo>> showCarts (HttpSession session) { Integer uid = getUserIdFromSession(session); List<CartVo> carts = cartService.queryCartByUid(uid); return new JsonResult <>(OK,carts); }
前端页面 ①用户点击了购物车,自动发送ajax请求,根据返回的内容填充购物车的内容
代码量过大,需要到cart.html的script标签中查看
删除商品 后端-持久层 1.sql语句编写
1 2 / / 根据cid删除指定商品delete from t_cart where cid = #{cid}
2.定义抽象方法
1 2 int deleteCartByCid (Integer cid) ;
3.编写mapper映射文件
后端-业务层 1.异常规划,删除失败
2.定义抽象方法
3.编写具体的业务逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 int deleteCartByCid (Integer cid) ;@Override public int deleteCartByCid (Integer cid) { int result = cartMapper.deleteCartByCid(cid); if (result == 0 ){ throw new DeleteException ("服务器异常,删除失败" ); } return result; }
4.单元测试
后端-控制层 1.已将异常加入过,不需要重复
2.设计请求
请求路径:/cart/deleteCart
请求参数:Integer[ ] cids
请求方式:post
响应类型:JsonResult< Void>
3.处理请求
1 2 3 4 5 6 7 8 9 @PostMapping("/deleteCart") public JsonResult<Void> deleteCartByCid (Integer[] cids) { for (Integer cid: cids) { cartService.deleteCartByCid(cid); } return new JsonResult <>(OK); }
4.单元测试
前端页面
考虑到提高代码复用性,决定前端无论单个还是多个cid都以参数传递,后端以数组接受
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 function delCartItem (cids ){ if (confirm ("确定要删除这条商品吗?" )){ $.ajax ({ url : "http://localhost:8080/cart/deleteCart" , type : "post" , data : "cids=" + cids, dataType : "json" , success :function (res ) { alert ("删除成功" ) showUserCarts (); $("#selectCount" ).empty ().html (0 ) $("#selectTotal" ).empty ().html (0 ) }, error :function (error ) { alert ("删除失败" ) } }) } } function selDelCart ( ){ let trNum = $("#cart-list tr" ).length let chooseNum = 0 ; let deletedCids = "" let char = "&" for (let i = 0 ; i < trNum; i++) { if ($("#cid" + i).prop ("checked" )){ chooseNum += 1 ; let cids = JSON .parse ($("#cid" +i).val ()); let str = "cids=" + cids if (i === trNum -1 ){ deletedCids += str }else { deletedCids += str + char } } } if (chooseNum === 0 ){ alert ("请先选择需要删除的购物车商品!!!" ) return false ; } if (confirm ("确定要删除这些商品吗?" )){ $.ajax ({ url : "http://localhost:8080/cart/deleteCart" , type : "post" , data : deletedCids, dataType : "json" , success :function (res ) { alert ("批量删除成功" ) showUserCarts (); $("#selectCount" ).html (0 ) $("#selectTotal" ).html (0 ) }, error :function (error ) { alert ("删除失败" ) } }) } }
数量增减 后端-持久层 1.编写sql语句
1 2 3 4 5 / / 根据cid更新用户商品数量update t_cart set num = #{num},modified_user = #{modifiedUser},modified_time = #{modifiedTime} where cid = #{cid} / / 根据cid查询用户cart信息select * from t_cart where cid = #{cid};
2.定义抽象方法
3.编写mapper映射文件
4.单元测试
后端-业务层 1.规划异常,更新失败,数据不存在
2.定义service接口的抽象方法
3.编写具体的处理逻辑(已开发过无需重复)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Cart queryCartByCid (Integer cid) ; @Override public int updateCartNumByCid (Integer num, String modifiedUser, Date modifiedTime, Integer cid) { Cart cart = cartMapper.queryCartByCid(cid); if (cart == null ){ throw new CartInfoNotExistsException ("购物车内无这条数据,增加失败" ); } return cartMapper.UpdateCartNumByCid(num,modifiedUser,modifiedTime,cid); }
4.单元测试
后端-控制层 1.处理异常,将异常加入到异常处理
2.设计请求
请求地址:/cart/updateCart
请求参数:Integer num,Integer cid,HttpSession session
请求类型:post
响应类型:JsonResult< void>
3.处理请求
1 2 3 4 5 6 7 @PostMapping("/updateCart") public JsonResult<Void> updateCateByCid (Integer num,Integer cid,HttpSession session) { String modifiedUser = getUsernameFromSession(session); Date modifiedTime = new Date (); cartService.updateCartNumByCid(num,modifiedUser,modifiedTime,cid); return new JsonResult <>(OK); }
4.单元测试
前端页面 ① 给增加和减少按钮绑定点击事件,在发送ajax请求之前先把数据减少或增加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 function addNum (num ) { var n = parseInt ($("#goodsCount" +num).val ()); $("#goodsCount" +num).val (n + 1 ); calcRow (num); let cid = $("#cid" ).val (); let updateNum = $("#goodsCount" +num).val () $.ajax ({ url : "http://localhost:8080/cart/updateCart" , type : "post" , dataType : "json" , data :{cid :cid,num :updateNum}, error : function ( ) { alert ("增加失败,请等待攻城狮修复!!" ) } }) } function calcRow (num ) { var vprice = parseFloat ($("#goodsPrice" +num).html ()); var vnum = parseFloat ($("#goodsCount" +num).val ()); var vtotal = vprice * vnum; $("#goodsCast" +num).html ("¥" + vtotal); }
商品结算 后端-持久层
关于展示用户收货地址所需要的在前面写好了因此在这不再重复
1.sql语句
1 2 3 4 5 6 / / 根据用户cid查询对应的cartVo信息SELECT c.cid,c.pid,c.`uid`,c.price,c.num,p.title,p.image,p.`price` AS real_price FROM t_cart c LEFT JOIN t_product p ON c.pid = p.`id` WHERE c.`cid` = #{cid} order by c.created_time desc
2.mapper抽象接口方法
1 2 CartVo queryCartVoByCid (Integer cid) ;
3.编写mapper的映射文件
后端-业务层 1.规划异常。没有异常
2.定义抽象接口
3.编写具体逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 CartVo queryCartVoByCid (Integer cid) ; List<Cart> queryCartByCids (Integer[] cids) ; @Override public CartVo queryCartVoByCid (Integer cid) { return cartMapper.queryCartVoByCid(cid); } @Override public List<CartVo> queryCartByCids (Integer[] cids) { List<CartVo> list = new ArrayList <>(); for (Integer cid : cids) { CartVo destCartVo = cartMapper.queryCartVoByCid(cid); list.add(destCartVo); } return list; }
后端-控制层 1.没有异常需要处理
2.设计请求
请求地址:/cart/queryCids
请求参数:Integer[] cids
请求类型:get
响应类型:JsonResult<List< Cart> >
3.处理请求
1 2 3 4 5 6 @GetMapping("/queryCids") public JsonResult<List<Cart>> queryCids (Integer[] cids) { List<Cart> list = cartService.queryCartByCids(cids); return new JsonResult <>(OK,list); }
前端页面 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <script type="text/javascript" > function showOrderItem ( ){ $.ajax ({ url : "http://localhost:8080/cart/queryCids" , type : "get" , dataType : "json" , data : location.search .substr (1 ), success :function (res ){ $("#cart-list" ).empty () for (let i = 0 ; i < res.data .length ; i++) { let str = "" ; let cartVo = res.data [i] let totalPrice = cartVo.price * cartVo.num str = "<tr>" + "<td><img src=.." + cartVo.image + "collect.png" + " class='img-responsive' /></td>" + "<td>" + cartVo.title + "</td>" + "<td>¥<span>" + cartVo.price + "</span></td>" + "<td>" + cartVo.num + "</td>" + "<td><span>" + totalPrice + "</span></td>" + "</tr>" $("#cart-list" ).append (str) } }, error : function ( ) { alert ("增加失败,请等待攻城狮修复!!" ) } }) } $(function ( ) { showOrderItem (); }) </script>
订单管理 创建数据表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 CREATE TABLE t_order ( oid INT AUTO_INCREMENT COMMENT '订单id' , uid INT NOT NULL COMMENT '用户id' , recv_name VARCHAR (20 ) NOT NULL COMMENT '收货人姓名' , recv_phone VARCHAR (20 ) COMMENT '收货人电话' , recv_province VARCHAR (15 ) COMMENT '收货人所在省' , recv_city VARCHAR (15 ) COMMENT '收货人所在市' , recv_area VARCHAR (15 ) COMMENT '收货人所在区' , recv_address VARCHAR (50 ) COMMENT '收货详细地址' , total_price BIGINT COMMENT '总价' , status INT COMMENT '状态:0-未支付,1-已支付,2-已取消,3-已关闭,4-已完成' , order_time DATETIME COMMENT '下单时间' , pay_time DATETIME COMMENT '支付时间' , created_user VARCHAR (20 ) COMMENT '创建人' , created_time DATETIME COMMENT '创建时间' , modified_user VARCHAR (20 ) COMMENT '修改人' , modified_time DATETIME COMMENT '修改时间' , PRIMARY KEY (oid) ) ENGINE= InnoDB DEFAULT CHARSET= utf8; CREATE TABLE t_order_item ( id INT AUTO_INCREMENT COMMENT '订单中的商品记录的id' , oid INT NOT NULL COMMENT '所归属的订单的id' , pid INT NOT NULL COMMENT '商品的id' , title VARCHAR (100 ) NOT NULL COMMENT '商品标题' , image VARCHAR (500 ) COMMENT '商品图片' , price BIGINT COMMENT '商品价格' , num INT COMMENT '购买数量' , created_user VARCHAR (20 ) COMMENT '创建人' , created_time DATETIME COMMENT '创建时间' , modified_user VARCHAR (20 ) COMMENT '修改人' , modified_time DATETIME COMMENT '修改时间' , PRIMARY KEY (id) ) ENGINE= InnoDB DEFAULT CHARSET= utf8;
创建实体类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Data public class Order extends BaseEntity { private Integer oid; private Integer uid; private String recvName; private String recvPhone; private String recvProvince; private String recvCity; private String recvArea; private String recvAddress; private Long totalPrice; private Integer status; private Date orderTime; private Date payTime; } @Data public class OrderItem extends BaseEntity { private Integer id; private Integer oid; private Integer pid; private String title; private String image; private Long price; private Integer num; }
创建订单 后端-持久层 1.sql语句编写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 / / 向order 表中插入一条order 数据insert into t_order(uid,recv_name,recv_phone,recv_province,recv_city,recv_area,recv_address, total_price,status,order_time,pay_time,created_user,created_time,modified_user,modified_time) values ( #{uid},#{recvName},#{recvPhone},#{recvProvince},#{recvCity},#{recvArea},#{recvAddress}, #{totalPrice},#{status},#{orderTime},#{payTime},#{createdUser},#{createdTime}, #{modifiedUser},#{modifiedTime}) / / 向order_item表中插入一条orderItem数据insert into t_order_item(oid,pid,title,image,price,num, created_user,created_time,modified_user,modified_time) values (#{oid},#{pid},#{title},#{image},#{price}, #{num},#{createdUser},#{createdTime},#{modifiedUser},#{modifiedTime} )
2.定义mapper接口和抽象方法
1 2 3 4 5 6 7 public interface OrderMapper { int insertOneOrder (Order order) ; int insertOneOrderItem (OrderItem orderItem) ; }
3.编写mapper的映射文件
设置自定义的结果集映射文件
4.单元测试
后端-业务层 1.规划异常,插入失败
2.定义service层接口和抽象方法
3.编写具体的处理逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 Order insertOrder (Integer aid, Long totalPrice, Integer uid, String username) ; int insertOrderItem (Integer oid, Integer cid, Integer num, String username) ;@Service public class IOrderServiceImpl implements IOrderService { @Autowired(required = false) private OrderMapper orderMapper; @Autowired(required = false) private IAddressService addressService; @Autowired(required = false) private ICartService cartService; @Autowired(required = false) private IProductService productService; @Override public Order insertOrder (Integer aid,Long totalPrice,Integer uid,String username) { Address address = addressService.queryAddressByAid(aid); Order order = new Order (); order.setUid(uid); order.setRecvName(address.getName()); order.setRecvPhone(address.getPhone()); order.setRecvProvince(address.getProvinceName()); order.setRecvCity(address.getCityName()); order.setRecvArea(address.getAreaName()); order.setRecvAddress(address.getAddress()); order.setTotalPrice(totalPrice); order.setStatus(0 ); Date createdTime = new Date (); order.setOrderTime(createdTime); order.setPayTime(null ); order.setCreatedUser(username); order.setModifiedUser(username); order.setCreatedTime(createdTime); order.setModifiedTime(createdTime); int result = orderMapper.insertOneOrder(order); if (result == 0 ){ throw new InsertException ("服务器出现错误,创建订单失败" ); } return orderMapper.queryOrderByOid(order.getOid()); } @Override public int insertOrderItem (Integer oid, Integer cid, Integer num, String username) { Cart cart = cartService.queryCartByCid(cid); Integer pid = cart.getPid(); Product product = productService.queryProductById(pid); OrderItem orderItem = new OrderItem (); orderItem.setOid(oid); orderItem.setPid(pid); orderItem.setTitle(product.getTitle()); orderItem.setImage(product.getImage()); orderItem.setPrice(product.getPrice()); orderItem.setNum(num); Date createdTime = new Date (); orderItem.setCreatedUser(username); orderItem.setCreatedTime(createdTime); orderItem.setModifiedUser(username); orderItem.setModifiedTime(createdTime); int result = orderMapper.insertOneOrderItem(orderItem); if (result == 0 ){ throw new InsertException ("服务器出现错误,创建订单失败" ); } return result; } }
4.单元测试
后端-控制层 1.处理异常,已添加无需再重复
2.设计请求
请求路径: ①/order/createOrder ②/order/createOrderItem
请求参数:①Integer aid,Long totalPrice,HttpSession session
②Integer oid,Integer cid,Integer pid,Integer num,HttpSession session
请求类型:post
响应类型:①JsonResult< Order> ②①JsonResult< Void>
3.创建一个新的控制器处理请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @PostMapping("/createOrder") public JsonResult<Order> createOrder (Integer aid,Long totalPrice,HttpSession session) { Integer uid = getUserIdFromSession(session); String username = getUsernameFromSession(session); orderService.insertOrder(aid,totalPrice,uid,username); return new JsonResult <>(OK); } @PostMapping("/createOrderItem") public JsonResult<Void> createOrderItem (Integer oid,Integer cid,Integer num,HttpSession session) { String username = getUsernameFromSession(session); orderService.insertOrderItem(oid,cid,num,username); return new JsonResult <>(OK); }
前端页面 ①在用户选择了商品来到确认订单页面之后
②确认商品无误之后,点击在线支付按钮,那么在点击之后就要生成order订单,
生成order订单项就应该发送一个ajax请求,且这个ajax请求生成的oid订单号需要返回给前端
在前端需要时带着这个订单号跳转到下一个指定页面
③且将oid传入另一个方法中,在这个方法内获取客户所勾选的商品的数量,
并根据这个数量将获取每一个商品cid,发送与商品个数一致的次数的ajax请求
并且每个ajax都需要带着cid和num和oid
javaScript代码太长,不予展示,需要到项目于的/web/orderConfirm.htmll页面查看
集成支付宝沙箱支付 ①引入依赖到pom文件
1 2 3 4 5 6 <dependency > <groupId > com.alipay.sdk</groupId > <artifactId > alipay-sdk-java</artifactId > <version > 4.23.26.ALL</version > </dependency >
②编写alipayConfig用于设置请求属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Data @Component public class AlipayConfig { public static String appId = "2021000121634026" ; public static String appPrivateKey = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDDnIWV/KsE+T3hpSr7+H//GdKsTbVcc/fpsp/RI9Aqfg0z009TrUI+NQ9+6mI4YxQUZnnUM1sRcnAQI4ivnGu2qkZFoot7gv50jwvZD8amIe6b3Joed2DBOKps2IJuyOwjV4Ae+WBJW5bSNiuuYu6UjanX69nK8TU6y6K39p02YnDqC47Z0veKVbAmpbsOnks1gGd+Cgcm5fSsdcx+aGKSt6YEJuYkqri9lUExHSYkZLNfzzAXRujyLCk0lLHdzUNDoatIyukk1HhCyvLosg3oaCtvIzqGC8/XHCEQ5r8JD4nKd/gGoyolWzhJvFcFkKmHjPZA8vT8TP1ZC1z1m1DvAgMBAAECggEBAI0C7JnvByoSsrVTZ+U0grDXYLOtYSxAvVrO1b7iXlIDhGjzz5+2qqZFgeIv/JZBdlwuc2yxiNjO8lHwC7zsugl4Pig8wOhMyjokVJopcT6Z/3SEVuXXkPw5aUIF4iES3oersESj6PF5AQSQ4HRaBTs51FI/R0WxFHpKCgcr1LE6inEn862CvZlbh2Cqh6rqBxB1ESi4eRhAj+FsJlczqWDCLn2dG1Ki7IhkuApdNFs9lurGeXGu/FBphaaprAiTVWuxyBWIdW5TYACjkNZ+oXrzCJK6t0DWsmxAbaZHMiLIVIAypZ6p8/kf/b70zXRYhkXQ6KUKpo983G6dBBPUF0ECgYEA7ta+jK3OZvzwZQeaBXYS4vi28jgpLg/v/DFpdcwaAShKEms/R7XZ8wZ9y4jabQKOuyJX69NS7dhfNv4wdW976BkIevwANM6haFSh5bJsq+EK8fwXEPez2uKPLsIdSgBwwh6En4vClOUTOOprLuyTXM7mQNqSW+AsaLmlaGYDuhcCgYEA0aqkcJMysXnGIc7nbJDiWvsRihe5WMsMCOY6vtjVPMh/qEasT9Hxhv4Gs+Pso15MNxhDuRddp+qgXyuUXGkeii8h+p2MgO5O5tA1T74APaDclmyiuc27Rj27Cc9mbFTWOk5txrXgzqEJjw9HudUHTJmpKoqI2aVEK73X6AdB3ukCgYAqHwk/+i8ajqU+zBZnvCkcikyJb0oj63+hdH1q3vH/HkHh+bQRS4sChzSMPrh23Sqa6jWjS4OmmrBAHJgjPeQWTMPoHKVUqtRgd/yNa+gqb+fkQVc4ENdRVP93eZh8wpMgSQ2OrbFFXRkEwqLghax/g6Wr7mA9f82VMphvTv59RQKBgA3YnxNwJSDjUdpZt57L0qb/faEJAAyFHD5aNfb0iuCAvS13vVloG/M2Q2sN2krPp2jcCVzn1h+Itx6R2jJgHswxYKUUUnsRQdSsW1jwy0NGpEqq0fRDSeLRoNB9Cd6Nm7guBcHhsP70U5VHBQ2Yq+q7GxjcHT2CVIYu+1svX4JBAoGARyBB9IjgM9dH0cH8djojC2qlH7Qar9bbvl55i9EQY51d1J/bC5JBE/CAYu0s1MmIatigcJ6A7FyvJ+nnow+qr4tegi5CG76v+Ue/IrgZXJZyDqMBrHorn/SwRXleOj2gjfxkQ8mh0OkrIntbdD6SDnvtXkxcVkfX9ICY5VIA4DA=" ; public static String alipayPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjtedJ8C1NIO3r4vuMThcvTMZxqO3Jki5VYPfkmnraA/PIKXXgsfOdoSWxCsiqPMIBMRT3Oyk1EsxgAeFBKTFaSM5LC8oinTXFbkv+3XEuOjtfqbp0oIgu9pWfJQDL2gIVSbm3VKmdE4UtJ36nu3hyuTT3U19QQsKVgxMDWHCOIw0eCHcJm1xDPj0zmagL3jC7576sXHcnFxEKARGugMpP9bkBgvFkjKrnkQfMAz3OO8vUSC0lCGo2UrSwhyD6zqXVz39sIduVpKTTg+wpAJQ/RhBhLXNw4JW3UaZpX2BZbmqEx91Hpr+O/95Z90cTqT+rwyu6uW612B5bCPnKa+BCQIDAQAB" ; public static String notifyUrl = "http://h64hsr.natappfree.cc/alipay/notifyNotice" ; public static String returnUrl = "http://h64hsr.natappfree.cc/alipay/returnNotice" ; public static String signType = "RSA2" ; public static String charset = "utf-8" ; public static String gatewayUrl = "https://openapi.alipaydev.com/gateway.do" ; }
③创建alipayController,并编写pay接口、异步回调接口、同步回调接口
由于在异步回调会出现session失效 的问题,解决方法 —> 如何解决异步回调session失效
异步接口方法一般用于在支付后进行数据库的修改操作,
而同步接口方法一般是用于向用户显示已支付完成的作用,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 @Slf4j @Controller @RequestMapping("/alipay") public class AliPayController { @Autowired private IOrderService orderService; @ResponseBody @GetMapping(value = "/pay",produces = "text/html;charset=UTF-8") public String goAlipay (String oid,String totalPrice,HttpServletRequest request) throws Exception { AlipayClient alipayClient = new DefaultAlipayClient (AlipayConfig.gatewayUrl, AlipayConfig.appId, AlipayConfig.appPrivateKey, "json" , AlipayConfig.charset, AlipayConfig.alipayPublicKey, AlipayConfig.signType); AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest (); alipayRequest.setReturnUrl(AlipayConfig.returnUrl); alipayRequest.setNotifyUrl(AlipayConfig.notifyUrl); String out_trade_no = oid; String total_amount = totalPrice; String subject = "支付宝沙箱测试商品支付" ; String body = "" ; String timeout_express = "1c" ; Integer str = (Integer) request.getSession().getAttribute("uid" ); String passback_params = String.valueOf(str); String uidStr = URLEncoder.encode(passback_params,"UTF-8" ); alipayRequest.setBizContent("{\"out_trade_no\":\"" + out_trade_no + "\"," + "\"total_amount\":\"" + total_amount + "\"," + "\"subject\":\"" + subject + "\"," + "\"body\":\"" + body + "\"," + "\"timeout_express\":\"" + timeout_express + "\"," + "\"passback_params\":\"" + uidStr + "\"," + "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}" ); String result = alipayClient.pageExecute(alipayRequest).getBody(); log.info(result); return result; } @PostMapping("/notifyNotice") @ResponseBody public void alipayNotifyNotice (HttpServletRequest request, HttpServletRequest response) throws Exception { log.info("支付成功, 进入异步通知接口..." ); Map<String, String> params = new HashMap <String, String>(); Map<String, String[]> requestParams = request.getParameterMap(); for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext();) { String name = (String) iter.next(); String[] values = (String[]) requestParams.get(name); String valueStr = "" ; for (int i = 0 ; i < values.length; i++) { valueStr = (i == values.length - 1 ) ? valueStr + values[i] : valueStr + values[i] + "," ; } params.put(name, valueStr); } boolean signVerified = AlipaySignature.rsaCheckV1(params, AlipayConfig.alipayPublicKey, AlipayConfig.charset, AlipayConfig.signType); if (signVerified) { String out_trade_no = new String (request.getParameter("out_trade_no" ).getBytes("ISO-8859-1" ), "UTF-8" ); String trade_no = new String (request.getParameter("trade_no" ).getBytes("ISO-8859-1" ), "UTF-8" ); String trade_status = new String (request.getParameter("trade_status" ).getBytes("ISO-8859-1" ), "UTF-8" ); String total_amount = new String (request.getParameter("total_amount" ).getBytes("ISO-8859-1" ), "UTF-8" ); String passback_params = new String (request.getParameter("passback_params" ).getBytes("ISO-8859-1" ), "UTF-8" ); String uidStr = URLDecoder.decode(passback_params,"UTF-8" ); if (trade_status.equals("TRADE_FINISHED" )) { } else if (trade_status.equals("TRADE_SUCCESS" )) { Integer uid = Integer.valueOf(uidStr); Integer oid = Integer.valueOf(out_trade_no); orderService.updateOrderStatusByOid(oid,uid,1 ); log.info("********************** 支付成功(支付宝异步通知) **********************" ); log.info("* 当前支付用户的id: {}" , passback_params); log.info("* 订单号: {}" , out_trade_no); log.info("* 支付宝交易号: {}" , trade_no); log.info("* 实付金额: {}" , total_amount); log.info("***************************************************************" ); } log.info("支付成功..." ); } else { log.info("支付, 验签失败..." ); } } @GetMapping("/returnNotice") public void alipayReturnNotice (HttpServletRequest request, HttpServletResponse response) throws Exception { log.info("支付成功, 进入同步通知接口..." ); Map<String, String> params = new HashMap <String, String>(); Map<String, String[]> requestParams = request.getParameterMap(); for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext();) { String name = (String) iter.next(); String[] values = (String[]) requestParams.get(name); String valueStr = "" ; for (int i = 0 ; i < values.length; i++) { valueStr = (i == values.length - 1 ) ? valueStr + values[i] : valueStr + values[i] + "," ; } valueStr = new String (valueStr.getBytes("ISO-8859-1" ), "utf-8" ); params.put(name, valueStr); } boolean signVerified = AlipaySignature.rsaCheckV1(params, AlipayConfig.alipayPublicKey, AlipayConfig.charset, AlipayConfig.signType); if (signVerified) { String out_trade_no = new String (request.getParameter("out_trade_no" ).getBytes("ISO-8859-1" ), "UTF-8" ); String trade_no = new String (request.getParameter("trade_no" ).getBytes("ISO-8859-1" ), "UTF-8" ); String total_amount = new String (request.getParameter("total_amount" ).getBytes("ISO-8859-1" ), "UTF-8" ); response.sendRedirect(request.getContextPath() + "/web/paySuccess.html?oid=" + out_trade_no); log.info("********************** 支付成功(支付宝同步通知) **********************" ); log.info("* 订单号: {}" , out_trade_no); log.info("* 支付宝交易号: {}" , trade_no); log.info("* 实付金额: {}" , total_amount); log.info("***************************************************************" ); } else { log.info("支付, 验签失败..." ); } } }
支付订单 后端-持久层 1.sql语句
1 2 3 4 5 6 7 8 9 10 11 #根据订单号查询订单 select * from t_order where oid = #{oid} #根据订单号修改状态 update t_order set status = #{status},pay_time = #{payTime} where oid = #{oid} #根据oid能从order_item表中找到对应的orderItem信息 SELECT * FROM t_order_item WHERE oid = #{oid} #根据uid和pid删除对应的t_cart表中的数据 DELETE FROM t_cart WHERE uid = #{uid} AND pid = #{pid}
2.定义mapper接口的抽象方法
1 2 3 4 5 6 7 8 Order queryOrderByOid (Integer oid) ; int updateStatusByOidInt (Integer oid, Integer status, Date payTime) ;List<OrderItem> queryOrderItemByOid (Integer oid) ; int deleteCartByUidAndPid (Integer uid,Integer pid) ;
3.编写对应的映射文件
4.单元测试
后端-业务层 1.规划异常,查询订单不存在,修改订单失败
2.定义service层的抽象方法
3.编写具体的业务逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 int deleteCartByUidAndPid (Integer uid,Integer pid) ;List<OrderItem> queryOrderItemByOid (Integer oid) ; int updateOrderStatusByOid (Integer oid,Integer uid,Integer status) ;@Override public int deleteCartByUidAndPid (Integer uid, Integer pid) { int result = cartMapper.deleteCartByUidAndPid(uid, pid); if (result == 0 ){ throw new DeleteException ("服务器异常,删除购物车商品失败!!" ); } return result; } @Override public List<OrderItem> queryOrderItemByOid (Integer oid) { List<OrderItem> orderItems = orderMapper.queryOrderItemByOid(oid); if (orderItems.size() == 0 ){ throw new OrderNotExistsException ("订单不存在!!!" ); } return orderItems; } @Override public int updateOrderStatusByOid (Integer oid, Integer uid, Integer status) { Order order = orderMapper.queryOrderByOid(oid); int result = 0 ; if (order.getStatus() == 0 ){ Date payTime = new Date (); result = orderMapper.updateStatusByOidInt(oid, status,payTime); List<OrderItem> orderItems = orderMapper.queryOrderItemByOid(oid); for (OrderItem o: orderItems) { Integer pid = o.getPid(); cartService.deleteCartByUidAndPid(uid, pid); } }else { result = orderMapper.updateStatusByOidInt(oid,status,order.getPayTime()); } if (result == 0 ){ throw new UpdateException ("服务器异常,修改订单状态失败" ); } return result; }
4.单元测试
后端-控制层 1.控制异常,将异常加入全局管理
2.设计请求
请求地址:①/order/queryOrder ②/order/updateStatus
请求参数:①Integer oid ;②Integet oid,HttpSession session,Integer status
请求类型:①get ②post
响应类型:①JsonResult< Order> ②JsonResult< Void>
3.处理请求
1 2 3 4 5 6 7 8 9 10 11 12 13 @GetMapping("/queryOrder") public JsonResult<Order> queryOrderByOid (Integer oid) { Order order = orderService.queryOrderByOid(oid); return new JsonResult <>(OK,order); } @PostMapping("/updateStatus") public JsonResult<Void> updateStatusByOid (Integer oid,Integer status) { orderService.updateOrderStatusByOid(oid,status); return new JsonResult <>(OK); }
前端页面 ①在页面加载完成的时候根据上个页面传递的参数查询order
②给确认付款按钮绑定单击事件,发送修改订单状态ajax请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 <script type="text/javascript" > function getOrderByOid (oid ){ $.ajax ({ url : "/order/queryOrder" , type : "get" , data : "oid=" + oid, dataType : "json" , success : function (res ) { if (res.status === 200 ){ let order = res.data $("#orderId" ).html (order.oid ) $("#orderPrice" ).html (order.totalPrice ) $("#rightPrice" ).html ("¥" + order.totalPrice + " " ) $("#oid" ).val (order.oid ) $("#totalPrice" ).val (order.totalPrice ) }else if (res.status === 3000 ){ location.href = "500.html" } }, error : function (error ) { if (error.status === 400 ){ location.href = "500.html" }else { alert ("服务器出现故障,请等待攻城狮修复!!" ) } } }); } $(function ( ) { $(".header" ).load ("components/head.html" ) $(".footer" ).load ("components/footer.html" ) $(".middleNavigation" ).load ("components/middleNavigationBar.html" ) let oid = getPidFromLastHtml (); getOrderByOid (oid); }) </script>
查看订单详情 后端-持久层 1.sql语句
1 2 3 4 5 6 7 8 9 10 #根据oid查询order信息 SELECT od.`oid`,od.`aid`,od.`recv_name`,od.`total_price`, od.`status`,od.`created_time`, orm.`image`,orm.`title`, orm.`price`,orm.`num` FROM t_order od LEFT JOIN t_order_item orm ON od.`oid` = orm.`oid` WHERE od.oid = #{oid} ORDER BY orm.`price` DESC;
2.定义mapper接口的抽象方法
1 2 List<OrderVo> queryOrderVoByOid (Integer oid) ;
3.编写对应的映射文件
后端-业务层 1.规划异常,没有新异常
2.定义service层的抽象接口和创建值对象OrderVo
3.编写具体的业务逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 List<OrderVo> queryOrderVoByOid (Integer oid) ; @Override public List<OrderVo> queryOrderVoByOid (Integer oid) { List<OrderVo> orderVos = orderMapper.queryOrderVoByOid(oid); for (OrderVo vo: orderVos) { Address address = addressService.queryAddressByAid(vo.getAid()); vo.setZip(address.getZip()); vo.setPhone(address.getPhone()); vo.setProvinceName(address.getProvinceName()); vo.setCityName(address.getCityName()); vo.setAreaName(address.getAreaName()); vo.setAddress(address.getAddress()); } return orderVos; }
后端-控制层 1.无异常,不需要处理
2.设计请求
请求路径:/order/queryOrderVo
请求参数:Integer oid
请求方式:get
响应类型:JsonResult<List< OrderVo>>
3.处理请求
1 2 3 4 5 6 7 @GetMapping("/queryOrderVo") public JsonResult<List<OrderVo>> queryOrderVo (Integer oid) { List<OrderVo> orderVos = orderService.queryOrderVoByOid(oid); return new JsonResult <>(OK,orderVos); }
前端页面
在这个页面加载完成之前需要先根据上个页面传来的参数查询订单信息
篇幅过长,不做展示,在/web/orderInfo.html页面下可查看
查看所有订单 后端-持久层 1.sql语句
1 2 3 4 5 6 7 8 9 SELECT od.`oid`,od.`aid`,od.`recv_name`,od.`total_price`, od.`status`,od.`order_time`, orm.`image`,orm.`title`, orm.`price`,orm.`num` FROM t_order od LEFT JOIN t_order_item orm ON od.`oid` = orm.`oid` WHERE od.uid = #{uid} ORDER BY od.`order_time` DESC;
2.定义抽象接口的方法
1 2 List<OrderVo> queryOrderVoByUid (Integer uid) ;
3.编写对应的mapper映射文件
4.单元测试
后端-业务层 1.无新异常
2.定义接口的抽象方法
3.编写具体业务逻辑
后端-控制层
前端页面
糙,这一个页面的js代码写的是真折磨人,写了快3个小时还有各种bug!
订单 的其余查看其他项所用的持久层、业务层、控制层已经在支付订单 中写好了
篇幅过长,不做展示,在/web/orders.html页面下可查看
收藏管理 创建数据表 1 2 3 4 5 6 7 8 9 10 CREATE TABLE t_favorites( fid INT PRIMARY KEY AUTO_INCREMENT COMMENT '收藏商品在数据表的id', uid INT COMMENT '归属的用户id', pid INT COMMENT '归属的商品id', image VARCHAR(255) COMMENT '商品图片保存地址', price BIGINT COMMENT '商品的价格', title VARCHAR(255) COMMENT '商品的标题', sell_point VARCHAR(255) COMMENT '商品的卖点', status INT COMMENT '商品的收藏状态' );
创建实体类 1 2 3 4 5 6 7 8 9 10 11 12 @Data public class Favorites { private Integer fid; private Integer uid; private Integer pid; private String image; private Long price; private String title; private String sellPoint; private Integer status; }
在Springboot中如何使用pageHelper插件
①从github上引入分页插件的依赖到项目的pom文件中
1 2 3 4 5 6 <dependency > <groupId > com.github.pagehelper</groupId > <artifactId > pagehelper</artifactId > <version > 3.7.4</version > </dependency >
②在yml配置文件中进行部分内容配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 mybatis: type-aliases-package: top.year21.computerstore.entity mapper-locations: classpath:mybatis/mapper/*.xml configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true pagehelper: dialect: mysql reasonable: true support-methods-arguments: true params: count=countSql
③在程序启动类中注册一个Pagehelper类,否则会报错或者取不到数据,这一步很关键!>
或者是在配置类(标注了@Configuration的类)中进行注册也可以
1 2 3 4 5 6 7 8 9 10 11 @Bean public PageHelper pageHelper () { PageHelper pageHelper = new PageHelper (); Properties properties = new Properties (); properties.setProperty("offsetAsPageNum" , "true" ); properties.setProperty("rowBoundsWithCount" , "true" ); properties.setProperty("reasonable" , "true" ); properties.setProperty("dialect" , "mysql" ); pageHelper.setProperties(properties); return pageHelper; }
④控制层调用业务层,业务层调用持久层查询,且分页功能必须在select查询语句之前开启,
所以在service层的实现类调用持久层的方法之前使用pagehelper.start方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Service public class IFavoritesServiceImpl implements IFavoritesService { @Autowired private FavoritesMapper favoritesMapper; @Autowired private IProductService productService; @Override public PageInfo<Favorites> queryFavorites (Integer uid, Integer pageNum,Integer pageSize,Integer status) { PageHelper.startPage(pageNum,pageSize); List<Favorites> favorites = favoritesMapper.queryFavoritesByUidAndStatus(uid, status); PageInfo<Favorites> pageInfo = new PageInfo <>(favorites); return pageInfo; }
⑤控制层将数据返回
1 2 3 4 5 6 7 8 9 10 11 12 @RestController @RequestMapping("/favorites") public class FavoritesController extends BaseController { @Autowired private IFavoritesService favoritesService; @GetMapping("/queryFavorites") public JsonResult<PageInfo<Favorites>> queryFavorites (HttpSession session, Integer pageNum,Integer pageSize,Integer status) { Integer uid = getUserIdFromSession(session); PageInfo<Favorites> favorites = favoritesService.queryFavorites(uid, pageNum,pageSize,status); return new JsonResult <>(OK,favorites); }
⑥分页功能如何使用到此结束
加入收藏 后端-持久层 1.sql语句编写
1 2 insert into t_favorite(uid,pid,image,price,title,sell_point,status) values(#{uid},#{pid},#{image},#{price},#{sellPoint},#{status})
2.定义抽象方法
1 2 int addFavorites (Favorites favorites) ;
3.编写对应的mapper映射文件
后端-业务层 1.异常规划,添加失败
2.定义抽象方法
3.编写具体业务处理逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 int addFavorites (Favorites favorites) ;@Override public int addFavorites (Integer uid,Integer pid) { Favorites favorites = new Favorites (); Product product = productService.queryProductById(pid); favorites.setUid(uid); favorites.setPid(pid); favorites.setImage(product.getImage()); favorites.setPrice(product.getPrice()); favorites.setTitle(product.getTitle()); favorites.setSellPoint(product.getSellPoint()); favorites.setStatus(1 ); int result = favoritesMapper.addFavorites(favorites); if (result == 0 ){ throw new InsertException ("服务器异常,收藏商品失败" ); } return favorites.getFid(); }
4.单元测试
后端-控制层 1.异常已添加过
2.设计请求
请求路径:/favorites/addFavorites
请求类型: HttpSession session,Integer pid
请求方式:post
响应类型:JsonResult< Integer>
3.处理请求
1 2 3 4 5 6 7 8 @PostMapping("/addFavorites") public JsonResult<Integer> addFavorites (HttpSession session,Integer pid) { Integer uid = getUserIdFromSession(session); int fid = favoritesService.addFavorites(uid, pid); return new JsonResult <>(OK,fid); }
前端页面 ①给加入收藏按钮绑定单击事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $("#btn-add-to-collect" ).click (function ( ) { $.ajax ({ url : "http://localhost:8080/favorites/addFavorites" , type : "post" , data : {pid :pid}, dataType : "json" , success : function (res ) { alert ("收藏成功!" ) }, error : function (err ) { alert ("服务器出现错误,加入购物车失败!" ) } }) })
收藏商品展示 后端-持久层 1.sql语句编写
1 2 #根据uid和收藏商品状态查询收藏的商品信息 select * from t_favorites where uid= #{uid} and status =#{status}
2.创建mapper文件和定义抽象方法
1 2 List<Favorites> queryFavoritesByUidAndStatus (Integer uid,Integer status) ;
3.编写对应的映射文件
4.单元测试
后端-业务层 1.规划异常,无异常
2.创建service层接口和定义抽象方法
3.编写具体的业务处理逻辑
1 2 3 4 5 6 7 8 9 10 11 12 PageInfo<Favorites> queryFavorites (Integer uid, Integer pageNum, Integer pageSize,Integer status) ; @Override public PageInfo<Favorites> queryFavorites (Integer uid, Integer pageNum,Integer pageSize,Integer status) { PageHelper.startPage(pageNum,pageSize); List<Favorites> favorites = favoritesMapper.queryFavoritesByUidAndStatus(uid, status); PageInfo<Favorites> pageInfo = new PageInfo <>(favorites); return pageInfo; }
后端-控制层 1.不需要处理异常
2.设计请求
请求路径:/favorites/queryFavorites
请求类型:Httpsession session,Integer status
请求方式:get
响应类型:JsonResult< Favorites>
3.处理请求
1 2 3 4 5 6 7 @GetMapping("/queryFavorites") public JsonResult<PageInfo<Favorites>> queryFavorites (HttpSession session, Integer pageNum,Integer pageSize,Integer status) { Integer uid = getUserIdFromSession(session); PageInfo<Favorites> favorites = favoritesService.queryFavorites(uid, pageNum,pageSize,status); return new JsonResult <>(OK,favorites); }
前端页面
js代码较长,到/web/favorites.html下可以查看
取消收藏
可以选择直接将数据库对应数据删除,但为了测试方便,决定添加一个字段status表示收藏状态
后端-持久层 1.sql语句编写
1 update t_favorites set status = #{status} where fid = #{fid} and uid = #{uid}
2.定义抽象方法
1 2 int updateFavoritesStatus (Integer status,Integer fid,Integer uid) ;
3.编写对应的mapper映射文件
后端-业务层 1.规划异常,修改状态失败
2.定义抽象方法
3.编写具体业务处理逻辑
1 2 3 4 5 6 7 8 9 10 11 12 int updateFavoritesStatus (Integer status,Integer fid,Integer uid) ;@Override public int updateFavoritesStatus (Integer status, Integer fid, Integer uid) { int result = favoritesMapper.updateFavoritesStatus(status, fid, uid); if (result == 0 ){ throw new UpdateException ("服务器异常,取消收藏失败" ); } return result; }
后端-控制层 1.异常无需进行处理,前面已经处理过
2.设计请求
请求路径:/favorites/updateStatus
请求参数:HttpSession session,Integer fid,Integer status
请求类型:post
响应类型:JsonResult< void>
3.处理请求
1 2 3 4 5 6 7 @PostMapping("/updateStatus") public JsonResult<Void> cancelFavorites (HttpSession session,Integer status,Integer fid) { Integer uid = getUserIdFromSession(session); favoritesService.updateFavoritesStatus(status,fid,uid); return new JsonResult <>(OK); }
前端页面 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function stopCollect (fid ){ if (confirm ("确定要取消对该商品的收藏吗?" )){ $.ajax ({ url : "http://localhost:8080/favorites/updateStatus" , type : "post" , data : {fid :fid,status :0 }, dataType : "json" , success : function (res ) { alert ("取消成功" ) location.reload (); }, error : function (err ) { alert ("服务器出现错误,取消失败!" ) } }) } }
加入购物车
持久层、业务层、控制层在购物车管理模块已经全部实现
前端页面
前端页面只需要将product.html页面的ajax请求进行cv,修改一下传递的参数即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function addCollectToCart (pid,price ){ if (confirm ("确定要将此商品加入购物车吗?" )){ $.ajax ({ url : "http://localhost:8080/cart/addCart" , type : "post" , data : {pid :pid,price :price,num :1 }, dataType : "json" , success : function (res ) { alert ("已成功加入购物车,在购物车等您结算哟!" ) }, error : function (err ) { alert ("服务器出现错误,加入购物车失败!" ) } }) } }
商品搜索 模糊搜索 后端-持久层 1.sql语句
1 2 3 4 5 SELECT id,title,sell_point,price,image FROM t_product WHERE STATUS = 1 AND title LIKE '%${title}%' ORDER BY priority DESC;
2.定义抽象方法
1 2 List<Product> queryProductByTitle (String title) ;
3.编写映射文件
4.单元检测
后端-业务层 1.简单查询无异常不需要规划
2.定义抽象方法
3.编写具体逻辑
1 2 3 4 5 6 7 8 9 10 11 12 PageInfo<Product> queryProductByTitle (Integer pageNum, Integer pageSize,String title) ; @Override public PageInfo<Product> queryProductByTitle (Integer pageNum, Integer pageSize,String title) { PageHelper.startPage(pageNum,pageSize);、 List<Product> products = productMapper.queryProductByTitle(title); return new PageInfo <>(products); }
4.单元测试
后端-控制层 1.无异常需要处理
2.设计请求
请求路径:/product/queryByTitle
请求参数:Integer pageNum,Integer pageSize,String title
请求类型:get
响应类型:JsonResult<PageInfo< Product>>
3.处理请求
1 2 3 4 5 6 7 8 @GetMapping("/{pageNum}/{pageSize}/{title}") public JsonResult<PageInfo<Product>> quertByTitle (@PathVariable("pageNum") Integer pageNum, @PathVariable("pageSize") Integer pageSize, @PathVariable("title") String title) { PageInfo<Product> lists = productService.queryProductByTitle(pageNum, pageSize, title); return new JsonResult <>(OK,lists); }
前端页面
前端页面搜索数据的展示、加入购物车、加入收藏基于其他页面疯狂copy,稍微改动即可完成
同样js代码过长,放在了/web/search.html页面下
抽离公共页面
①新建一个名为head.html(简称head)的文件,将需要抽离页面的head标签内的全部内容粘贴到
head文件中,并且在抽离页面的原处使用一个div进行占位,可以在任意地方js的load方法进行引入
②新建一个名为middleNavigation.html(简称middle)的文件,将需要抽离页面的导航标签内的内容粘贴到
middle文件中,并且在抽离页面的原处使用一个div进行占位,可以在任意地方js的load方法进行引入
③新建一个名为footer.html(简称footer)的文件,将需要抽离页面的footer标签内的全部内容粘贴到
footer文件中,并且在抽离页面的原处使用一个div进行占位,可以在任意地方js的load方法进行引入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <div class ="header" > </div > <div class ="middleNavigation" > </div > <div class ="footer" > </div > <script type ="text/javascript" > $(function ( ) { $(".header" ).load ("components/head.html" ) $(".footer" ).load ("components/footer.html" ) $(".middleNavigation" ).load ("components/middleNavigationBar.html" ) }) </script >
PS:因为是把整体都抽离了,因此有些页面需要一开始就要加载的功能可以使用js代码在
head或者footer文件进行加载,如下完整的head.html页面所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 <div class ="row" > <div class ="col-md-3" > <a href ="index.html" > <img src ="../images/index/stumalllogo.png" /> </a > </div > <div class ="col-md-9 top-item" > <ul id ="topMenu" class ="list-inline pull-right" > <li > <a href ="favorites.html" > <span class ="fa fa-heart" > </span > 收藏</a > </li > <li class ="li-split" > |</li > <li > <a href ="orders.html" > <span class ="fa fa-file-text" > </span > 订单</a > </li > <li class ="li-split" > |</li > <li > <a href ="cart.html" > <span class ="fa fa-cart-plus" > </span > 购物车</a > </li > <li class ="li-split" > |</li > <li > <div class ="btn-group" > <button type ="button" class ="btn btn-link dropdown-toggle" data-toggle ="dropdown" > <span id ="top-dropdown-btn" > <span class ="fa fa-gears" > </span > 管理 <span id ="menuCaret" class ="caret" > </span > </span > </button > <ul id ="uiMenu" class ="dropdown-menu top-dropdown-ul" role ="menu" > <li > <a href ="password.html" > 修改密码</a > </li > <li > <a href ="userdata.html" > 个人资料</a > </li > <li > <a href ="upload.html" > 上传头像</a > </li > <li > <a href ="address.html" > 收货管理 </a > </li > </ul > </div > </li > <li class ="li-split" > |</li > <li > <span class ="fa fa-user" > </span > <a href ="login.html" id ="loginStatus" > </a > </li > </ul > </div > </div > <script > $(function ( ) { if (sessionStorage.getItem ("user" ) === null ){ $("#loginStatus" ).html (" " + "登录" ) $("#menuCaret" ).removeClass ("caret" ) $("#uiMenu" ).removeClass ("dropdown-menu top-dropdown-ul" ).empty () }else { $.ajax ({ url : "http://localhost:8080/user/queryUser" , type : "get" , dataType : "json" , success : function (res ) { changeMenu (res) } }); } }) function changeMenu (res ) { let user = res.data ; if (user.username != null ){ $("#loginStatus" ).html (" " + user.username ) let exitStr = "<li class=\"li-split\">|</li>" + "<li>" + "<span class=\"fa fa-sign-out\"></span>" + "<a href=\"javascript:void(0)\" onclick=\"exitLogin()\"> 退出</a>" + "</li>" $("#topMenu" ).append (exitStr) document .getElementById ("loginStatus" ).removeAttribute ("href" ) } } function exitLogin ( ){ if (sessionStorage.getItem ("user" ) == null ){ alert ("尚未登录,请先登录!" ) }else { $.ajax ({ url : "http://localhost:8080/user/exit" , type : "get" , dataType : "json" , success :function (res ) { if (res.status === 200 ){ alert ("退出成功!" ) sessionStorage.removeItem ("user" ) location.href = "index.html" } }, error :function ( ) { alert ("服务器出现未知异常,退出登录失败" ) } }) } } </script >
引入kaptcha验证码
①引入依赖包到pom文件
1 2 3 4 5 6 <dependency > <groupId > com.github.penggle</groupId > <artifactId > kaptcha</artifactId > <version > 2.3.2</version > </dependency >
②创建并编写kaptcha的配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package top.year21.computerstore.config;import com.google.code.kaptcha.impl.DefaultKaptcha;import com.google.code.kaptcha.util.Config;import lombok.extern.slf4j.Slf4j;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.util.Properties;@Slf4j @Configuration public class KaptchaConfig { @Bean public DefaultKaptcha getKaptcheCode () { DefaultKaptcha defaultKaptcha = new DefaultKaptcha (); Properties properties = new Properties (); properties.setProperty("kaptcha.border" , "no" ); properties.setProperty("kaptcha.textproducer.font.color" , "black" ); properties.setProperty("kaptcha.image.width" , "100" ); properties.setProperty("kaptcha.image.height" , "36" ); properties.setProperty("kaptcha.textproducer.font.size" , "30" ); properties.setProperty("kaptcha.obscurificator.impl" , "com.google.code.kaptcha.impl.ShadowGimpy" ); properties.setProperty("kaptcha.session.key" , "code" ); properties.setProperty("kaptcha.noise.impl" , "com.google.code.kaptcha.impl.NoNoise" ); properties.setProperty("kaptcha.background.clear.from" , "232,240,254" ); properties.setProperty("kaptcha.background.clear.to" , "232,240,254" ); properties.setProperty("kaptcha.textproducer.char.length" , "4" ); properties.setProperty("kaptcha.textproducer.font.names" , "彩云,宋体,楷体,微软雅黑" ); Config config = new Config (properties); defaultKaptcha.setConfig(config); return defaultKaptcha; } }
③创建并编写kaptcha控制层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 package top.year21.computerstore.controller;import com.google.code.kaptcha.Constants;import com.google.code.kaptcha.Producer;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import javax.imageio.ImageIO;import javax.servlet.ServletOutputStream;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.awt.image.BufferedImage;@Slf4j @RestController @RequestMapping("/kaptcha") public class KaptchaController { @Autowired private Producer producer; @GetMapping("/kaptcha-image") public void getKaptchaImage (HttpServletRequest request, HttpServletResponse response) throws Exception { response.setDateHeader("Expires" , 0 ); response.setHeader("Cache-Control" , "no-store, no-cache, must-revalidate" ); response.addHeader("Cache-Control" , "post-check=0, pre-check=0" ); response.setHeader("Pragma" , "no-cache" ); response.setContentType("image/jpeg" ); String capText = producer.createText(); log.info("******************当前验证码为:{}******************" , capText); request.getSession().setAttribute(Constants.KAPTCHA_SESSION_KEY, capText); BufferedImage bi = producer.createImage(capText); ServletOutputStream out = response.getOutputStream(); ImageIO.write(bi, "jpg" , out); try { out.flush(); } finally { out.close(); } } }
④在前端调用kaptcha的控制层接口
1 2 3 4 5 6 7 <img id="kaptcha" src="http://localhost:8080/kaptcha/kaptcha-image" onclick="reFlashImg('kaptcha')" /> function reFlashImg (imgId ) { let kaptcha = document .getElementById (imgId) kaptcha.src = "http://localhost:8080/kaptcha/kaptcha-image?time=" + new Date (); }
统计业务方法耗时
切面方法 切面方法就是在切面类的方法,所谓的切面类就是增强类
1.切面方法修饰符必须是public
2.切面方法的返回值可以是void或Object,但如果这个方法被@Around环绕通知注解所修饰,那么
这个方法必须声明为Object类型,除此之外,随意。
3.切面方法的方法名可以自定义
4.切面方法可以接受参数,参数是ProccedingJoinPoint接口类型的参数,但是被@Around环绕通知
注解所修饰的方法必须要传递这个参数,除此之外,随意。
怎么实现AOP操作? 怎么做?
①导入aop的依赖到pom文件中,不需要版本,springboot会自动进行版本仲裁
1 2 3 4 5 6 7 8 9 <dependency > <groupId > org.aspectj</groupId > <artifactId > aspectjweaver</artifactId > </dependency > <dependency > <groupId > org.aspectj</groupId > <artifactId > aspectjtools</artifactId > </dependency >
②创建切面类(增强类),并在这个类中定义切面方法(编写增强逻辑)
③进行通知的配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Aspect @Component public class TimerAspect { @Around("execution(* top.year21.computerstore.service.impl.*.*(..))") public Object around (ProceedingJoinPoint pjp) throws Throwable { long startedTime = System.currentTimeMillis(); Object result = pjp.proceed(); long endTime = System.currentTimeMillis(); System.out.println("业务方法总共耗时:" + (endTime - startedTime)); return result; } }
④启动项目,随机访问一个 进行测试
汇总信息 1.注册 单元测试报错提示:Invalid bound statement (not found)
说明:本次是由于没有在yml配置文件中设置mapper映射文件对应的位置
解决方法:将mapper映射文件的位置在yml配置文件中进行对应的设置
1 2 mybatis: mapper-locations: classpath:mybatis/mapper/*.xml
2.注册 单元测试报错:nested exception is org.apache.ibatis.binding.BindingException: Parameter ‘xxx‘ not found
说明:由于传参的是实体类对象,因此实体类对象不需要用@Param修饰
3.登录 由于还没设计表单就使用postman测试后端业务层接口时,报错提示状态码415,Unsupported Media Type
说明:业务层的控制器方法的形参加了 @RequestBody注解 ,只能解析json类型的数据,而在postman中
测试发送的请求Content-Type类型是multipart/form-data; 所以才导致了这个错误
解决方法:将对应的控制器方法形参的 @RequestBody注解去掉即可
4.登录 写完js代码后发现点击事件没有绑定成功
解决方法:原因在于js代码没有设置为在页面加载完成后执行
5.修改个人资料 的js代码不是很熟悉
val()用于获取标签中的value属性的值
6.在上传头像页面,表单使用sumbit提交知道action默认跳转行为如何阻止? —>解决表单默认提交跳转行为
7.关于@PathVariable、@RequestParam、@Param注解
@PathVariable、@RequestParam用于后端控制层与前端页面交互时获取请求参数使用
@Param是用于后端业务层和持久层交互时,sql语句填充占位符所用
8.js中的serialize()方法、FormData类
serialize()方法:可以将表单数据自动拼接成key=value的形式提交给服务器,但一般提交的都是
普通控件类型中的数据(text/password/radio/checkbox)等等
FormData类:将表单中的数据保持原有结构的形式进行提交。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 Data data = new FormData ($("#form" )[0 ]); $.ajax ({ url : "http://localhost:8080/test" , type : "post" , data : new FormData ($("#form" )[0 ]), processData : false , contentType : false , dataType : "json" , success : function (res ){ alert (res.status ",修改成功" ) $("#img" ).attr ("src" ,res.data ) }, error : function (err ){ alert ("修改失败" ) } })
9.如果后端持久层接收的是一个实体类对象的参数,必须在标签中使用parameterType=”实体类对象”,
这样才能根据前端传值和实体类对象的属性匹配自动完成注入,不然保存
10.给当一个方法的参数是必须在标签内填写,怎么解决?
使用正则表达式替换,能够替换的前提是str这个串中必须包含对应的占位符信息
1 2 3 4 str = "<a href='javascript:void(0);' onclick='deleteAddress(#{deleteAid},#{isDefault})' " str = str.replace ("#{deleteAid}" ,address.aid ) str = str.replace ("#{isDefault}" ,address.isDefault )
11.如何给a标签绑定事件并携带参数呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 "<a href='javascript:void(0);' onclick='updateAddress(#{editAid})' >" function updateAddress (aid ){ jumpWithParam (aid); } function jumpWithParam (param ){ url = "editAddress.html?aid=" + param; location.href = url; } function showThisUserAddress ( ){ var hrefUrl = location.href ; var param = hrefUrl.split ("=" ) var aid = decodeURI (param[1 ]); }
12.当在进行多表查询之后,查询的结果集无法和任何实体类进行映射怎么办?
重新创建一个Value Object(值对象)与查询出来的结果集形成映射关系
VO(Value Object):用于接收无法和任何实体类形成映射关系的vo对象,实际上就是根据结果集的字段,
对应创建一个有相对应属性的一个实体类
13.当前端发送的值是多个同名属性时,后端应该怎么接收?
根本没想到前端可以直接在ajax请求的data直接发送cids=5&cids=4&cids=6这样的数据,惊呆了
e.g. http:localhost:8080/cart/queryCids?cids=5&cids=4
后端可以以一个同参数名的数组进行接收
e.g. public JsonResult<List< Cart>> queryCids(Integer[] cids){}
14.持久层需要的数据,如果在业务层的逻辑中可以手动生成,那么在业务层的形参列表中就不必要求输入
15.业务层需要的数据可以根据持久层的抽象方法的形参列表进行对比得出
16.如何阻止form表单使用sumbit默认跳转的解决方式二
通过给form表单设置一个id,并使用id选择器给这个form表单绑定一个onsumbit提交事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 function checkIsNotChoose ( ){ let chooseNum = $("input[type='checkbox']:checked" ).length if (chooseNum === 0 ){ alert ("请先选择需要结算的购物车商品!!!" ) return false ; } } <form id="searchForm" onsubmit="return checkIfHaveVal()" action= "http://localhost:8080/web/search.html" class ="form-inline pull-right" role="form" ></form> <script type ="text/javascript" > function checkIfHaveVal ( ){ let val = $("#search" ).val () if (val === '' ) { alert ("请先输入搜索内容!" ) return false ; } } </script >
.children(expr) 例如:children(“:eq(4)”)
children()是一个筛选器,顾名思义就是筛选孩子,筛选那些符合条件的孩子。
其中children是筛选器的名称,expr是表达式,所有选择器中的表达式都可以用在这,
比如按标签名"div",按类名".class",按序号":first"等等,
如果表达式为空,那么返回的是所有的孩子,返回的结果仍为jQuery对象。
18.模糊查询报错Could not set parameters for mapping
模糊查询,只能使用${},若使用#{},占位符会被解析成?,当中参数里面的一部分,导致报错
如果想要强行使用#{},只能这么写 like concat(‘%’,#{username},’%’) ;
或者 like “%”#{username}”%”;
19.拦截器白名单失效假象
表现是在拦截中配置了对某些资源和接口放行,但发现使用浏览器请求还被重定向拦截器指定页面
原因如下:我们访问一个页面时候 springboot发现我们这个页面不存在自动会跳转至error页面 ,
这个时候跳转至error页面其实是被拦截器拦截了,所以会觉得是excludePathPatterns失效了。
我们只需要白名单放行的路径中把error页面排除
1 2 3 4 5 6 7 8 9 10 11 registry.addInterceptor(new LoginInterceptor ()) .addPathPatterns("/**" ) .excludePathPatterns("/web/login.html" ,"/web/index.html" , "/web/register.html" ,"/web/product.html" , "/web/components/**" ,"/web/search.html" , "/user/**" ,"/address/**" ,"/file/**" ,"/district/**" , "/images/**" ,"/js/**" ) .excludePathPatterns("/error" );