确认订单 1.确认订单-持久层 1.1规划需要执行的SQL语句
用户在购物车列表页中通过随机勾选相关的商品,在点击”结算”按钮后跳转到”确认订单页”,在这个页面中需要展示用户在上个页面所勾选的”购物车列表页”中对应的数据.说白了也就是列表展示,且展示的内容还是来自于购物车表.但是用户勾选了哪些商品呢,所以”购物车列表页”需要将用户勾选的商品id传递给”确认订单页”
所以在持久层需要完成“根据若干个不确定的id值,查询购物车数据表,显示购物车中的数据信息”。则需要执行的SQL语句大致是。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 select cid, uid, pid, t_cart.price, t_cart.num, title, t_product.price as realPrice, imagefrom t_cartleft join t_product on t_cart.pid = t_product.idwhere cid in (?,?,?)order by t_cart.created_time desc
注意where cid in (?,?,?),这里是需要传入cid的集合
1.2设计接口和抽象方法 在CartMapper接口中添加findVOByCids抽象方法
1 List<CartVO> findVOByCids (Integer[] cids) ;
1.3配置映射 1.在CartMapper.xml文件中添加SQL语句的映射配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <select id ="findVOByCids" resultType ="com.cy.store.vo.CartVO" > select cid, uid, pid, t_cart.price, t_cart.num, title, t_product.price as realPrice, image from t_cart left join t_product on t_cart.pid = t_product.id where cid in ( <foreach collection ="array" item ="cid" separator ="," > #{cid} </foreach > ) order by t_cart.created_time desc</select >
foreach循环就是一个for循环
collection标识循环的是list集合还是数组,如果是list集合就用collection=“list”
item用来接收每次循环获取的值
separator标识循环出来的值中间用什么隔开,且最后循环出来的值后面不加
1.4单元测试 在CartMapperTests测试类中添加findVOByCids方法进行测试
1 2 3 4 5 6 7 8 @Test public void findVOByCids () { Integer[] cids = {1 , 2 , 6 , 8 , 100 }; List<CartVO> list = cartMapper.findVOByCids(cids); for (CartVO item : list) { System.out.println(item); } }
2.确认订单-业务层 2.1规划异常 查询语句,没有需要规划的异常,在业务层判断这几条购物车商品的数据归属是否正确,如果不正确也不需要抛出异常,直接从查询到的数据中移除该商品就行了
2.2设计接口和抽象方法及实现 1.在ICartService接口中添加getVOByCids()抽象方法
1 List<CartVO> getVOByCids (Integer uid, Integer[] cids) ;
2.在CartServiceImpl类中重写业务接口中的抽象方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Override public List<CartVO> getVOByCids (Integer uid, Integer[] cids) { List<CartVO> list = cartMapper.findVOByCids(cids); Iterator<CartVO> it = list.iterator(); while (it.hasNext()) { CartVO cart = it.next(); if (!cart.getUid().equals(uid)) { it.remove(); } } return list; }
2.3单元测试 业务层只是调用持久层获取数据并判断归属是否正确,这里不再测试
3.确认订单-控制层 3.1处理异常 业务层没有抛出异常,所以不需要处理异常
3.2设计请求
/carts/list
GET
Integer[] cids, HttpSession session
JsonResult<List>
3.3处理请求 1.在CartController类中添加处理请求的getVOByCids()方法。
1 2 3 4 5 @RequestMapping("list") public JsonResult<List<CartVO>> findVOByCids (Integer[] cids, HttpSession session) { List<CartVO> data = cartService.getVOByCids(getUidFromSession(session), cids); return new JsonResult <>(OK, data); }
启动服务,登录后在地址栏输入http://localhost:8080/carts/list?cids=1&cids=5&cids=7进行测试
4.确认订单-前端页面 4.1显示勾选的购物车数据 1.检查cart.html页面,里面form标签的action=”orderConfirm.html”属性(规定表单数据提交到哪里)和结算按钮的类型”type=submit”是必不可少的,这样点击”结算”时才能将数据传给”确认订单页”并在”确认订单页”展示选中的商品数据
2.在orderConfirm.html页面中实现自动加载从cart.html页面中传递过来的cids数据,再去请求ajax,然后将后端返回的数据填充在页面的某个区域中
3.orderConfirm.js文件中
$(“.link-pay”).click(……)作用:点击”在线支付”后跳转到支付页面,这个其实就是下个模块要做的”创建订单”功能,该功能需要和数据库交互,所以不是在前端实现的,所以这行代码无用
$(“.link-success”).click(…):在orderConfirm.html页面没有class为link-success的标签,所以这行代码不会被执行
综上两条,orderConfirm.js文件在orderConfirm.html页面中无用,但存在可能会和下个模块”创建订单”功能冲突(下个模块会实现点击”创建订单”后页面跳转),所以注释掉
下面在orderConfirm.html页面编写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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 <script type="text/javascript" > $(document ).ready (function ( ) { showCartList (); }); function showCartList ( ) { $("#cart-list" ).empty (); $.ajax ({ url : "/carts/list" , type : "GET" , data : location.search .substr (1 ), dataType : "JSON" , success : function (json ) { if (json.state == 200 ) { var list = json.data ; console .log (location.search .substr (1 )); var allCount = 0 ; var allPrice = 0 ; for (var i = 0 ; i < list.length ; i++) { var tr = '<tr>\n' + '<td><img src="..#{image}collect.png" class="img-responsive" /></td>\n' + '<td>#{title}</td>\n' + '<td>¥<span>#{price}</span></td>\n' + '<td>#{num}</td>\n' + '<td><span>#{totalPrice}</span></td>\n' + '</tr>' ; tr = tr.replace ("#{image}" ,list[i].image ); tr = tr.replace ("#{title}" ,list[i].title ); tr = tr.replace ("#{price}" ,list[i].realPrice ); tr = tr.replace ("#{num}" ,list[i].num ); tr = tr.replace ("#{totalPrice}" ,list[i].realPrice *list[i].num ); $("#cart-list" ).append (tr); allCount += list[i].num ; allPrice += list[i].realPrice *list[i].num ; } $("#all-count" ).html (allCount); $("#all-price" ).html (allPrice); } }, error : function (xhr ) { alert ("在确认订单页加载勾选的购物车数据时发生未知的异常" +xhr.status ); } }); } </script>
1.为什么点击购物车列表页面的”结算”按钮后地址栏中会请求http://localhost:8080/web/orderConfirm.html?cids=6&cids=5呢,因为该按钮有一个type=submit属性,且表单有一个action="orderConfirm.html"属性,所以点击该按钮后会携带表单中参数自动跳转
会携带哪些参数呢:把表单中有name属性的标签的value值传递出去,针对这个请求传递的是name”cids”,其value值根据勾选的商品而定,可以是1或3或10
2.data: location.search.substr(1)这个API的参数为0表示截取地址栏中?后面的数据,即参数
如果这个API的参数为0则表示截取地址栏中?前面的数据,即请求地址
4.2显示选择收货地址 收货地址存放在前端的一个select下拉列表中,我们需要将查询到的当前登录用户的收货地址动态的加载到这个下拉列表中.从数据库的角度看,是一个select查询语句,在”收货地址列表展示”模块已经编写了该持久层,业务层,控制层,所以这里只需要编写对应的前端页面就可以了
1.在orderConfirm.html页面中的ready函数中添加showAddressList方法的调用,使确认订单页加载时能够自动从后端获取该用户地址填充到select控件中并将第一个地址显示出来
1 2 3 4 $(document ).ready (function ( ) { showCartList (); showAddressList (); });
2.在orderConfirm.html页面中编写showAddressList方法
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 function showAddressList ( ) { $("#address-list" ).empty (); $.ajax ({ url : "/addresses" , type : "GET" , dataType : "JSON" , success : function (json ) { if (json.state == 200 ) { var list = json.data ; for (var i = 0 ; i < list.length ; i++) { var opt = '<option value="#{aid}">#{name} #{tag} #{provinceName}#{cityName}#{areaName}#{address} #{tel}</option>' ; opt = opt.replace ("#{aid}" ,list[i].aid ); opt = opt.replace ("#{name}" ,list[i].name ); opt = opt.replace ("#{tag}" ,list[i].tag ); opt = opt.replace ("#{provinceName}" ,list[i].provinceName ); opt = opt.replace ("#{cityName}" ,list[i].cityName ); opt = opt.replace ("#{areaName}" ,list[i].areaName ); opt = opt.replace ("#{address}" ,list[i].address ); opt = opt.replace ("#{tel}" ,list[i].tel ); $("#address-list" ).append (opt); } } }, error : function (xhr ) { alert ("在确认订单页加载用户地址时发生未知的异常" +xhr.status ); } }); }
创建订单 1.创建数据表 1.使用use命令先选中store数据库。
2.在store数据库中创建t_order和t_order_item数据表
针对该模块可以将t_order_item表和t_order表合并,但是以后可能开发某个模块可能单独用到t_order_item(比如用户查看订单时只需要t_order_item表就可以实现)所以,建议这两个表分开创建
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;
2.创建用户的实体类 1.entity包下创建Order实体类并继承BaseEntity类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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; }
2.在com.cy.store.entity包下创建OrderItem实体类并继承BaseEntity类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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; }
3.创建订单-持久层 3.1规划需要执行的SQL语句 1.插入订单数据的SQL语句
1 inert into t_order (aid除外的所有字段) values (字段的值)
2.插入某一个订单中商品数据的SQL语句
1 inert into t_order (id除外的所有字段) values (字段的值)
3.2实现接口和抽象方法 在mapper包下创建OrderMapper接口并在接口中添加抽象方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public interface OrderMapper { Integer insertOrder (Order order) ; Integer insertOrderItem (OrderItem orderItem) ; }
3.3编写映射 创建OrderMapper.xml文件,并添加抽象方法的映射
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 <?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 ="com.cy.store.mapper.OrderMapper" > <insert id ="insertOrder" useGeneratedKeys ="true" keyProperty ="oid" > 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} ) </insert > <insert id ="insertOrderItem" useGeneratedKeys ="true" keyProperty ="id" > 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} ) </insert > </mapper >
3.4单元测试 创建OrderMapperTests测试类并添加测试方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @RunWith(SpringRunner.class) @SpringBootTest public class OrderMapperTests { @Autowired private OrderMapper orderMapper; @Test public void insertOrder () { Order order = new Order (); order.setUid(31 ); order.setRecvName("小王" ); order.setRecvPhone("133333" ); orderMapper.insertOrder(order); } @Test public void insertOrderItem () { OrderItem orderItem = new OrderItem (); orderItem.setOid(1 ); orderItem.setPid(10000001 ); orderItem.setTitle("高档铅笔" ); orderMapper.insertOrderItem(orderItem); } }
4.创建订单-业务层 4.1规划异常 无异常
4.2实现接口和抽象方法及实现
查看订单表的字段从而分析业务层方法需要哪些参数:
oid:主键自增,所以不需要该参数
uid:由控制层获取session中uid传给业务层,所以需要该参数 recv_name:通过”确认订单页”传递选中的地址aid,根据aid在在业务层调用已经声明的findByAid方法(该方法是在做”设置默认地址”模块时创建的,只在持久层创建了,并没有在业务层继续实现),所以需要参数aid recv_phone:同上 recv_province:同上 recv_city:同上 recv_area:同上 recv_address:同上 total_price:根据前端传来的cids查询出每类商品数量和单价,然后相乘后求和,所以需要参数Integer[] cids status:默认是0,所以不需要该参数 order_time:业务层实现方法内部可以声明,所以不需要该参数 pay_time:”创建订单”模块不需要此参数 created_user:由控制层获取session中username传给业务层,所以需要该参数 created_time:业务层实现方法内部可以声明,所以不需要该参数 modified_user:由控制层获取session中username传给业务层,所以需要该参数 modified_time:业务层实现方法内部可以声明,所以不需要该参数
综上分析,需要的参数是uid和aid,且需要在IAddressService接口添加getByAid()方法来获取选中的收货地址的详细数据:
1.在IAddressService接口中添加getByAid()方法
1 Address getByAid (Integer aid, Integer uid) ;
2.在AddressServiceImpl类中实现接口中的getByAid()抽象方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Override public Address getByAid (Integer aid, Integer uid) { Address address = addressMapper.findByAid(aid); if (address == null ) { throw new AddressNotFoundException ("收货地址数据不存在的异常" ); } if (!address.getUid().equals(uid)) { throw new AccessDeniedException ("非法访问" ); } address.setProvinceCode(null ); address.setCityCode(null ); address.setAreaCode(null ); address.setCreatedUser(null ); address.setCreatedTime(null ); address.setModifiedUser(null ); address.setModifiedTime(null ); return address; }
3.在service包下创建IOrderService业务层接口并添加抽象方法用于创建订单
1 2 3 public interface IOrderService { Order create (Integer aid, Integer[] cids, Integer uid, String username) ; }
返回值是Order是因为还要在下个页面展示订单详细信息
4.在impl包下创建OrderServiceImpl并编写代码实现订单和订单中所有商品数据的插入操作
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 @Service public class OrderServiceImpl implements IOrderService { @Autowired private OrderMapper orderMapper; @Autowired private IAddressService addressService; @Autowired private ICartService cartService; private IUserService userService; @Override public Order create (Integer aid, Integer[] cids, Integer uid, String username) { List<CartVO> list = cartService.getVOByCids(uid, cids); long totalPrice = 0L ; for (CartVO cartVO : list) { totalPrice += cartVO.getRealPrice()*cartVO.getNum(); } Address address = addressService.getByAid(aid, uid); 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.setOrderTime(new Date ()); order.setStatus(0 ); order.setTotalPrice(totalPrice); order.setCreatedUser(username); order.setCreatedTime(new Date ()); order.setModifiedUser(username); order.setModifiedTime(new Date ()); Integer rows = orderMapper.insertOrder(order); if (rows != 1 ) { throw new InsertException ("插入数据时产生未知的异常" ); } for (CartVO cartVO : list) { OrderItem orderItem = new OrderItem (); orderItem.setOid(order.getOid()); orderItem.setPid(cartVO.getPid()); orderItem.setTitle(cartVO.getTitle()); orderItem.setImage(cartVO.getImage()); orderItem.setPrice(cartVO.getRealPrice()); orderItem.setNum(cartVO.getNum()); orderItem.setCreatedUser(username); orderItem.setCreatedTime(new Date ()); orderItem.setModifiedUser(username); orderItem.setModifiedTime(new Date ()); rows = orderMapper.insertOrderItem(orderItem); if (rows != 1 ) { throw new InsertException ("插入数据时产生未知的异常" ); } } return order; } }
4.3单元测试 创建OrderServiceTests测试类并添加create()方法进行功能测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @SpringBootTest @RunWith(SpringRunner.class) public class OrderServiceTests { @Autowired private IOrderService orderService; @Autowired IUserService userService; @Test public void create () { Integer[] cids = {2 ,4 ,6 }; Order order = orderService.create(13 , cids, 11 , "小红" ); System.out.println(order); } }
5.创建订单-控制层 5.1处理异常 没有异常需要处理
5.2设计请求
/orders/create
GET
Integer aid, Integer[] cids, HttpSession session
JsonResult
5.3处理请求 controller包下创建OrderController类,并继承自BaseController类,在类中编写请求方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @RestController @RequestMapping("orders") public class OrderController extends BaseController { @Autowired private IOrderService orderService; @RequestMapping("create") public JsonResult<Order> create (Integer aid, Integer[] cids, HttpSession session) { Order data = orderService.create( aid, cids, getUidFromSession(session), getUsernameFromSession(session)); return new JsonResult <>(OK,data); } }
6.创建订单-前端页面 在”确认订单页”添加发送请求的处理方法使点击”在线支付”按钮可以创建订单并跳转到”支付信息页”(支付页显示详细商品信息这个功能这里不做了)
请求参数是通过字符串拼接得到的,那么就必须用get请求,因为post请求不能拼接字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 $("#btn-create-order" ).click (function ( ) { var aid = $("#address-list" ).val (); var cids = location.search .substr (1 ); $.ajax ({ url : "/orders/create" , data : "aid=" + aid + "&" + cids, type : "GET" , dataType : "JSON" , success : function (json ) { if (json.state == 200 ) { location.href = "payment.html" ; } else { alert ("创建订单失败!" + json.message ); } }, error : function (xhr ) { alert ("创建订单数据时产生未知的异常" + xhr.status ); } }); });
AOP 检测项目所有业务层方法的耗时(开始执行时间和结束执行时间只差值),再在不改变项目主体流程代码的前提条件下完成此功能,就要用到AOP
如果我们想对业务某一些方法同时添加相同的功能需求,并且在不改变业务功能逻辑的基础之上进行完成,就可以使用AOP的切面编程进行开发
1.Spring AOP AOP:面向切面(Aspect)编程。AOP并不是Spring框架的特性(Spring已经被整合到了SpringBoot中,所以如果AOP是Spring框架的特性,那么就不需要手动导包,只需要在一个类上写@Aspect注解,鼠标放到该注解上按alt+enter就可以自动导包了,但是事与愿违,所以说AOP并不是Spring框架的特性),只是Spring很好的支持了AOP。
使用步骤:
首先定义一个类,将这个类作为切面类
在这个类中定义切面方法(5种:前置,后置,环绕,异常,最终)
将这个切面方法中的业务逻辑对应的代码进行编写和设计
通过连接点来连接目标方法,就是用粗粒度表达式和细粒度表达式来进行连接
2.切面方法 1.切面方法的访问权限是public。
2.切面方法的返回值类型可以是void或Object,如果该方法被@Around注解修饰,必须使用Object作为返回值类型,并返回连接点方法的返回值;如果使用的注解是@Before或@After等其他注解时,则自行决定。
3.切面方法的名称可以自定义。
4.切面方法可以接收参数,参数是ProccedingJoinPoint接口类型的参数.但是@Around所修饰的方法必须要传递这个参数.其他注解修饰的方法要不要该参数都可以
3 统计业务方法执行时长 1.因为AOP不是Spring内部封装的技术,所以需要进行导包操作:在pom.xml文件中添加两个关于AOP的依赖aspectjweaver和aspectjtools。
1 2 3 4 5 6 7 8 <dependency > <groupId > org.aspectj</groupId > <artifactId > aspectjweaver</artifactId > </dependency > <dependency > <groupId > org.aspectj</groupId > <artifactId > aspectjtools</artifactId > </dependency >
2.在com.cy.store.aop包下创建TimerAspect切面类,给类添加两个注解进行修饰:
@Aspect(将当前类标记为切面类)
@Component(将当前类的对象创建使用维护交由Spring容器维护)
1 2 3 4 @Aspect @Component public class TimerAspect { }
3.在类中添加切面方法,这里使用环绕通知的方式来进行编写
参数ProceedingJoinPoint接口表示连接点,也就是是目标方法的对象
1 2 3 4 5 6 7 8 9 10 public Object around (ProceedingJoinPoint pjp) throws Throwable { long start = System.currentTimeMillis(); Object result = pjp.proceed(); long end = System.currentTimeMillis(); System.out.println("耗时:" +(end-start)); return result; }
4.将当前环绕通知映射到某个切面上,也就是指定连接的点.给around方法添加注解@Around
1 @Around("execution(* com.cy.store.service.impl.*.*(..))")
第一个*表示方法返回值是任意的
第二个*表示imp包下的类是任意的
第三个*表示类里面的方法是任意的
(…)表示方法的参数是任意的
5.启动项目,在前端浏览器访问任意一个功能模块进行功能的测试