Springboot写电商系统(2)
Springboot写电商系统(2)
- 1.新增收货地址
- 1.创建t_addresss数据库表
- 2.创建Address实体类
- 3.数据库操作的持久层
- 1.接口写抽象方法
- 2.xml写方法映射sql
- 3.测试
- 4.前后数据交互的业务层
- 1.sql操作的异常抛出
- 2.交互方法的接口定义
- 3.接口的方法实现
- 4.测试
- 5.与前端交互的控制层
- 1.添加请求成功的异常响应
- 2.处理请求
- 6.前端ajax请求
- 7.后端获取省市区三级菜单
- 1.创建地址表
- 2.创建实体类
- 3.sql持久层
- 4.前后端数据操作的业务层
- 5.响应前端的控制层
- 6.前端请求
- 2.删除收货地址
- 1.持久层
- 2.业务层
- 3.控制层
- 4.前端请求和渲染
- 3.显示购物车列表
- 1.VO实体类
- 2.持久层
- 3.业务层
- 4.控制层
- 5.前端请求与渲染
- 4.补充知识点
1.新增收货地址
1.创建t_addresss数据库表
name是关键字,所以需要用``
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;
2.创建Address实体类
记得要继承BaseEntity类(里面有四个字段)。
把除了BaseEntity里面的四个字段外的所有字段在这个实体类里添加,然后创建他们的get,set;equals和hashCode;toString。
实体类的字段名要符合驼峰命名,所以会有和数据库字段名不一样的,记得要在持久层做字段名的映射。
/**收货地址额实体类*/
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;/*** get,set* equals和hashCode* toString*/
}
3.数据库操作的持久层
新增收获地址之前要判断一下这个uid用户的收货地址是否已经有20个了,如果是就不能再新增;如果是0,那么用户添加的第一个就要默认地址赋值1,如果是1-20,那默认地址就要赋值0。所以这里面设计到2个SQL语句:
insert into t_address (aid以外的所有字段) values (字段值)
select count(*) from t_address where uid=?
1.接口写抽象方法
在接口AddressMapper里面写上面两个SQL语句的抽象方法,第一个传递参数address,第二个传递uid。
public interface AddressMapper {Integer insert (Address address);Integer countByUid(Integer uid);
}
2.xml写方法映射sql
在AddressMapper.xml里面首先写xml对mybatis和上面接口的映射。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.store.mapper.AddressMapper">
</mapper>
然后在resultMap标签里面写表字段和实体字段的映射(这里写名字不一样的字段和自增的主键字段aid),type写实体类的全路径。
<resultMap id="AddressEntityMap" type="com.example.store.entity.Address"><id column="aid" property="aid"/><result column="province_name" property="provinceName"/><result column="province_code" property="provinceCode"/><result column="city_name" property="cityName"/><result column="city_code" property="cityCode"/><result column="area_name" property="areaName"/><result column="area_code" property="areaCode"/><result column="is_default" property="isDefault"/><result column="created_user" property="createdUser"/><result column="created_time" property="createdTime"/><result column="modified_user" property="modifiedUser"/><result column="modified_time" property="modifiedTime"/></resultMap>
最后写上面2个方法的sql映射,id对应接口里定义的方法名,有自增主键的要加上useGeneratedKeys和keyProperty,定义返回类型的加上resultType。
<insert id="insert" useGeneratedKeys="true" keyProperty="aid">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}, #{provinceName}, #{provinceCode}, #{cityName}, #{cityCode}, #{areaName},#{areaCode}, #{zip}, #{address}, #{phone}, #{tel}, #{tag}, #{isDefault}, #{createdUser},#{createdTime}, #{modifiedUser}, #{modifiedTime})</insert><!--resultType="java.lang.Integer"不写会报错,因为Integer不是基本数据类型--><select id="countByUid" resultType="java.lang.Integer">select count(*) from t_address where uid=#{uid}</select>
3.测试
首先给AddressMapperTests添加两个注解,一个是声明是测试类最终不会打包的@SpringBootTest,一个是可以运行的测试类@RunWith(SpringRunner.class);然后通过@Autowired自动装配Mapper,就可以使用mapper里面的方法了。
@SpringBootTest
@RunWith(SpringRunner.class)
public class AddressMapperTests {@Autowiredprivate AddressMapper addressMapper;@Testpublic void insert(){Address address=new Address();address.setUid(1);address.setName("zoe");address.setAddress("Los Angeles");addressMapper.insert(address);}@Testpublic void countByUid(){Integer count=addressMapper.countByUid(1);System.err.println(count);}
}
4.前后数据交互的业务层
拿到前端数据进行数据插入时会出现错误uid的用户不存在,这个异常UsernameNotFoundException已存在。
当用户插入数据超过20条时不可以再添加,所以需要添加异常AddressCountLimitException。
插入操作中出现的异常InsertException已存在。
1.sql操作的异常抛出
需要添加一个AddressCountLimitException异常并继承ServiceException ,然后重构5个构造方法。
public class AddressCountLimitException extends ServiceException {/**重写ServiceException的所有构造方法*/
}
2.交互方法的接口定义
在IAddressService 接口里面定义新增地址的方法addNewAddress,这里面从前端拿到的表单信息是address(这里面有name是收货人),然后从session中拿到uid和username(这个将是修改人和创建人)。
public interface IAddressService {void addNewAddress(Integer uid, String username, Address address);
}
3.接口的方法实现
首先要给这个接口实现类添加注解@Service,之后spring才能找到这个bean。
在AddressServiceImpl里面实现接口方法。业务层里面将会用到持久层里面定义的sql方法,所以将用到方法的mapper(接口)导入并用@Autowired自动装载。
这里用到了一个超参:用户收获地址的最大条数,在application.properties里面设置user.address.max-count=20;然后在类里面通过注解将值赋值到变量maxCount中。
@Service//交给spring管理,所以需要在类上加@Service
public class AddressServiceImpl implements IAddressService {@Autowiredprivate AddressMapper addressMapper;@Autowiredprivate UserMapper userMapper;@Value("${user.address.max-count}")private Integer maxCount;@Overridepublic void addNewAddress(Integer uid, String username, Address address) {User result=userMapper.findByUid(uid);if(result==null||result.getIsDelete()==1){throw new UsernameNotFoundException("用户数据不存在");}Integer count=addressMapper.countByUid(uid);if(count>=maxCount){throw new AddressCountLimitException("用户收货地址超出上限");}Integer isDefault=count==0?1:0;address.setIsDefault(isDefault);address.setUid(uid);address.setCreatedUser(username);address.setModifiedUser(username);address.setCreatedTime(new Date());address.setModifiedTime(new Date());Integer rows=addressMapper.insert(address);if (rows!=1){throw new InsertException("插入用户收获地址时发生未知异常");}}
}
4.测试
还是先添加测试的两个注解@SpringBootTest,@RunWith(SpringRunner.class),然后自动装载@Autowired业务层的接口,在测试方法里使用接口方法测试。
@SpringBootTest
@RunWith(SpringRunner.class)
public class AddressServiceTests {@Autowiredprivate IAddressService addressService;@Testpublic void addNewAddress(){Address address=new Address();address.setName("老王");addressService.addNewAddress(2,"zoe",address);}
}
5.与前端交互的控制层
1.添加请求成功的异常响应
首先在业务层的时候有一个异常抛出,所以在控制层里面要把这个异常添加到响应的异常里面,在BaseController里添加:
else if (e instanceof AddressCountLimitException) {result.setState(4003);result.setMessage("用户的收货地址超出上限的异常");
}
2.处理请求
首先AddressController要继承BaseController。
然后添加注解@RequestMapping("addresses"),@RestController,用到了业务层的接口,所以用@Autowired自动装配,添加功能的响应地址@RequestMapping("add_new_address"),然后拿到前端数据进行处理。
@RequestMapping("addresses")
@RestController
public class AddressController extends BaseController{@Autowiredprivate IAddressService addressService;@RequestMapping("add_new_address")public JsonResult<Void>addNewAddress(Address address, HttpSession session){Integer uid=getUidFromSession(session);String username=getUsernameFromSession(session);addressService.addNewAddress(uid,username,address);return new JsonResult<>(OK);}
}
浏览器中输入http://localhost:8080/addresses/add_new_address?name=tom&phone=987进行测试。
6.前端ajax请求
<script>$("#btn-add-new-address").click(function () {$.ajax({url: "/addresses/add_new_address",type: "POST",data: $("#form-add-new-address").serialize(),dataType: "JSON",success: function (json) {if (json.state == 200) {alert("新增收货地址成功")} else {alert("新增收货地址失败")}},error: function (xhr) {alert("新增收货地址时产生未知的异常!"+xhr.message);}});});</script>
然后就可以提交表单进行测试。
7.后端获取省市区三级菜单
现在的三级菜单功能是写在了前端的js里面,但实际业务中是要把这些数据写在数据库里面的,所以现在需要实现这个功能,首先把前端这个js代码删除掉:
<script type="text/javascript" src="../js/distpicker.data.js"></script>
<script type="text/javascript" src="../js/distpicker.js"></script>
然后开始实现这个功能,这里前端用户选择省市区之后有一个联动效果,这个就是通过父code查到子name的操作。
还有一个就是用户选择省市区之后传递给后端的其实是一个code,要补全数据库内容的话还需要有一个自己code查自己name的操作。所以这里的sql将是两个操作。
1.创建地址表
parent代表父区域的代码号;code代表自身的代码号;省的父代码号是+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;
然后将这些规定好的数据插入到表中:
LOCK TABLES t_dict_district WRITE;
INSERT INTO t_dict_district VALUES (1,'110100','110101','东城区'),(2,'110100','110102','西城区')等等等等;
UNLOCK TABLES;
2.创建实体类
这里就不用再继承了,因为这个表里面没有那四个是字段,这个表只是用来查询父子地址的。
因为没有继承BaseEntity所以需要实现接口Serializable序列化。
添加表字段之后,添加get,set; equals和hashCode; toString方法。
public class District implements Serializable {private Integer id;private String parent;private String code;private String name;
}
3.sql持久层
为了之后这个功能的复用,将这个新建持久层DistrictMapper接口,然后写mapper接口和sql映射
public interface DistrictMapper {List<District> findByParent(String parent);String findNameByCode(String code);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.store.mapper.DistrictMapper"><select id="findByParent" resultType="com.example.store.entity.District">select * from t_dict_district where parent=#{parent} order by code ASC</select><select id="findNameByCode" resultType="java.lang.String">select name from t_dict_district where code=#{code}</select>
</mapper>
测试持久层:
@SpringBootTest
@RunWith(SpringRunner.class)
public class DistrictMapperTests {@Autowiredprivate DistrictMapper districtMapper;@Testpublic void findByParent(){List<District> list=districtMapper.findByParent("86");for (District d:list){System.out.println(d);}}@Testpublic void findNameByCode(){String name= districtMapper.findNameByCode("610000");System.out.println(name);}
}
4.前后端数据操作的业务层
首先查询的时候不会有什么异常,所以直接定义抽象接口方法,然后实现方法。从前端拿到parent(父的code)然后查询并返回数据,这里返回数据的时候将每一条子数据的id和parent都置为null,利于数据的传输,所以每个子数据传到前端的就是这个子的code和name。
public interface IDistrictService {List <District> findByParent(String parent);String findNameByCode(String code);
}
@Service
public class DistrictServiceImpl implements IDistrictService {@Autowiredprivate DistrictMapper districtMapper;@Overridepublic List<District> findByParent(String parent) {List<District> list=districtMapper.findByParent(parent);for (District d :list){d.setId(null);d.setParent(null);}return list;}@Overridepublic String findNameByCode(String code) {return districtMapper.findNameByCode(code);}
}
测试业务层:
@SpringBootTest
@RunWith(SpringRunner.class)
public class DistrictServiceTests {@Autowiredprivate IDistrictService districtService;@Testpublic void findByParent(){List<District> list= districtService.findByParent("86");for (District d:list){System.out.println(d);}}@Testpublic void findNameByCode(){String name=districtService.findNameByCode("610000");System.out.println(name);}
}
这里要分开讨论,首先是联动选择,当前端选择一个父code到后端之后,后端需要拿到子code和那么,这个和前端交互,所以需要写一个controller,当前端有动作请求时后端就要判断是不是要用这个控制器来返回给前端子name数据。
但是通过code找到自己的name,这个数据不需要传递给前端,只需要在后端把这个name数据添加到district对象上,然后保存到数据库。所以第二个code找name的方法不用到控制层,在Address添加地址的时候,把前端传来的code找到name,然后添加到数据库即可。所以在AddressServiceImpl新增收货地址的时候用这个服务实现功能:
@Autowired
private IDistrictService districtService;String provinceName=districtService.findNameByCode(address.getProvinceCode());String cityName = districtService.findNameByCode(address.getCityCode());String areaName = districtService.findNameByCode(address.getAreaCode());address.setProvinceName(provinceName);address.setCityName(cityName);address.setAreaName(areaName);
5.响应前端的控制层
首先是没有新的error,所以不用处理异常,直接继承BaseConrtroller即可,然后开始处理响应,
@RequestMapping("districts")
@RestController
public class DistrictController extends BaseController {@Autowiredprivate IDistrictService districtService;@RequestMapping({"/",""})public JsonResult<List<District>> findByParent(String parent){List<District> list=districtService.findByParent(parent);return new JsonResult<>(OK,list);}
}
输入http://localhost:8080/districts?parent=86网址进行测试。
6.前端请求
进入页面显示省的列表加载,然后省的组件有改变的话就向后端发送请求市,同理获取第三级数据。
var defaultOption="<option value='0'>-----请选择-----</option>";// option标签并不会把内容发送到后端,而是将value值发送给后端,所以用value表示当前这个区域的code值$(document).ready(function () {showProvinceList();//调用这个方法,给省列表数据$("#province-list").append(defaultOption);//将省,市,县的下拉列表内容设为"-----请选择-----"$("#city-list").append(defaultOption);$("#area-list").append(defaultOption);});function showProvinceList() {$.ajax({url: "/districts",type: "POST",data: "parent=86",//发送请求用于获取所有省对象,一级菜单dataType: "JSON",success: function (json) {if (json.state == 200) {var list = json.data;//获取所有省对象的List集合for (var i = 0; i < list.length; i++) {var opt ="<option value='"+list[i].code+"'>"+list[i].name+"</option>";$("#province-list").append(opt);}} else {alert("省/直辖区的信息加载失败")}}});}$("#province-list").change(function () {//省组件有改变,先获取到省区域父代码号var parent = $("#province-list").val();$("#city-list").empty();$("#area-list").empty();$("#city-list").append(defaultOption);$("#area-list").append(defaultOption);if (parent == 0) {//如果继续程序,后面的ajax接收的json数据中的data是空集合[],进不了for循环,没有任何意义,所以直接在这里终止程序return;}$.ajax({url: "/districts",type: "POST",data: "parent="+parent,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='"+list[i].code+"'>"+list[i].name+"</option>";$("#city-list").append(opt);}} else {alert("市的信息加载失败")}}});});$("#city-list").change(function () {var parent = $("#city-list").val();$("#area-list").empty();$("#area-list").append(defaultOption);if (parent == 0) {return;}$.ajax({url: "/districts",type: "POST",data: "parent="+parent,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='"+list[i].code+"'>"+list[i].name+"</option>";$("#area-list").append(opt);}} else {alert("县的信息加载失败")}}});});
2.删除收货地址
设为默认地址和删除收货地址有很多相似之处,我就只写一下删除吧。首先是sql语句的设计。
1.持久层
删除的话就是通过aid删除数据,但如果删除的数据是默认地址(通过getIsDefault拿到真否),那么就要得到modeified_time最近的那一条数据作为默认(拿到之后设为默认,设默认这个功能我已经实现了,可以自己写一下),
那么些接口和映射文件,limit 0,1表示查询到的第一条数据(limit (n-1),pageSize),这样查询后就只会获得第一条数据。
select 整个数据时,一定要有resultMap="AddressEntityMap",对实体和表进行映射。
Integer deleteByAid(Integer aid);
Address findLastModified(Integer uid);
<delete id="deleteByAid">delete from t_address where aid=#{aid}
</delete>
<select id="findLastModified" resultMap="AddressEntityMap">select * from t_address where uid=#{uid} order by modified_time DESC limit 0,1
</select>
2.业务层
这里sql执行会出现的异常包括:没有该条地址数据(已开发),地址数据归属错误(已开发)和删除的时候可能会产生未知的异常(DeleteException )。
public class DeleteException extends ServiceException{/**重写ServiceException的所有构造方法*/
}
然后实现delete这个功能:
void delete(Integer aid,Integer uid,String username);
@Overridepublic void delete(Integer aid, Integer uid, String username) {Address result=addressMapper.findByAid(aid);if (result == null) {throw new AddressNotFoundException("收货地址数据不存在");}if (!result.getUid().equals(uid)) {throw new AccessDeniedException("非法数据访问");}Integer rows = addressMapper.deleteByAid(aid);if (rows != 1) {throw new DeleteException("删除数据时产生未知的异常");}if (result.getIsDefault() == 0) {return;}Integer count = addressMapper.countByUid(uid);if (count == 0) {return;}Address address = addressMapper.findLastModified(uid);rows = addressMapper.updateDefaultByAid(address.getAid(), username, new Date());if (rows != 1) {throw new UpdateException("更新数据时产生未知的异常");}}
3.控制层
添加delete的异常到BaseController:
else if (e instanceof DeleteException) {result.setState(5002);result.setMessage("删除数据时产生未知的异常");
}
然后是后端响应:/addresses/{aid}/set_default(以前的数据是通过表单直接提交的,还有一种提交方式就是RestFul风格,这种提交方式可以提交更多的数据,这里用这个提交方式)。
RestFul编写时不管参数名和占位符是否一致都必须加@PathVariable(“aid”)
@RequestMapping("{aid}/delete")
public JsonResult<Void> delete(@PathVariable("aid") Integer aid,HttpSession session) {addressService.delete(aid,getUidFromSession(session),getUsernameFromSession(session));return new JsonResult<>(OK);
}
4.前端请求和渲染
给显示列表的"删除"按钮添加onclick属性并指向deleteByAid(aid)方法。
<td><a onclick="delete_(#{aid})" class="btn btn-xs add-del btn-info"><span class="fa fa-trash-o"></span> 删除</a></td>
在显示列表的for循环中为占位符aid赋值。
tr = tr.replace("#{aid}",list[i].aid);
然后写点击了删除按钮和触发的delete_函数,这里不能写delete,因为这是一个关键字。
function delete_(aid) {$.ajax({url: "/addresses/"+aid+"/delete",type: "POST",dataType: "JSON",success: function (json) {if (json.state == 200) {//重新加载收货地址列表页面showAddressList();} else {alert("删除收货地址失败")}},error: function (xhr) {alert("删除收货地址时产生未知的异常!"+xhr.message);}});}
3.显示购物车列表
这里涉及到一个多表查询,用到了cart和product两个表的信息,所以表不用建,但是这两个表组成的数据是什么类型的实体呢(即不是product也不是cart),所以在store包下创建一个vo包,在该包下面创建CartVO类,不需要继承BaseController类,那相应的就需要单独实现Serializable接口。
VO全称Value Object,值对象。当进行select查询时,查询的结果属于多张表中的内容,此时发现结果集不能直接使用某个POJO实体类来接收,因为POJO实体类不能包含多表查询出来的信息,解决方式是:重新去构建一个新的对象,这个对象用于存储所查询出来的结果集对应的映射,所以把这个对象称之为值对象。
1.VO实体类
首先分析前端需要的数据都有哪些,哪些是cart表里的数据,哪些是product表里的数据,最后在生成他们的getset,equal hash和tostring
public class CartVO implements Serializable {private Integer cid;//购物车页面的勾选功能,要有cidprivate Integer uid;//要拿到某个用户的购物车信息,要有uidprivate Integer pid;//购物车操作后要更新product表里面的库存等信息,所以要有pidprivate Long price;//这个价格是用户加入购物车时的商品价格private Integer num;//这个num是购物车里这个用户这个商品的数量,和product里的num不一样(库存量)private String title;//这个是product表里的信息,要展示在购物车列表的前端private Long realPrice;//这个是product表里的价格private String image;//这个是product表里的信息,要展示在购物车列表的前端
}
2.持久层
首先在CartMapper接口中定义方法,这个方法是通过uid查询到上面vo实体数据,然后两表关联的时候不希望有null数据,所以以cart表作为主表(左连接)查询此用户的vo信息。
List<CartVO> findVoByUid(Integer uid);
<select id="findVoByUid" resultType="com.example.store.vo.CartVO">select cid,uid,pid,t_cart.price,t_cart.num,title,t_product.price as realPrice,imagefrom t_cart left join t_product on t_cart.pid=t_product.idwhere uid=#{uid}order by t_cart.created_time desc</select>
3.业务层
List<CartVO> getVOByUid(Integer uid);
@Overridepublic List<CartVO> getVOByUid(Integer uid) {return cartMapper.findVoByUid(uid);}
4.控制层
@RequestMapping({"", "/"})
public JsonResult<List<CartVO>> getVOByUid(HttpSession session) {List<CartVO> data = cartService.getVOByUid(getUidFromSession(session));return new JsonResult<List<CartVO>>(OK, data);
}
5.前端请求与渲染
<script type="text/javascript">$(document).ready(function() {showCartList();});//展示购物车列表数据function showCartList() {$("#cart-list").empty();$.ajax({url: "/carts",type: "GET",dataType: "JSON",success: function(json) {var list = json.data;for (var i = 0; i < list.length; i++) {var tr = '<tr>\n' +'<td>\n' +'<input name="cids" value="#{cid}" type="checkbox" class="ckitem" />\n' +'</td>\n' +'<td><img src="..#{image}collect.png" class="img-responsive" /></td>\n' +'<td>#{title}#{msg}</td>\n' +'<td>¥<span id="goodsPrice#{cid}">#{singlePrice}</span></td>\n' +'<td>\n' +'<input type="button" value="-" class="num-btn" οnclick="reduceNum(1)" />\n' +'<input id="goodsCount#{cid}" type="text" size="2" readonly="readonly" class="num-text" value="#{num}">\n' +'<input class="num-btn" type="button" value="+" οnclick="addNum(#{cid})" />\n' +'</td>\n' +'<td><span id="goodsCast#{cid}">#{totalPrice}</span></td>\n' +'<td>\n' +'<input type="button" οnclick="delCartItem(this)" class="cart-del btn btn-default btn-xs" value="删除" />\n' +'</td>\n' +'</tr>';tr = tr.replaceAll(/#{cid}/g, list[i].cid);tr = tr.replaceAll(/#{image}/g, list[i].image);tr = tr.replaceAll(/#{title}/g, list[i].title);tr = tr.replaceAll(/#{singlePrice}/g, list[i].realPrice);tr = tr.replaceAll(/#{num}/g, list[i].num);tr = tr.replaceAll(/#{totalPrice}/g, list[i].realPrice * list[i].num);if (list[i].realPrice < list[i].price) {tr = tr.replace(/#{msg}/g, "比加入时降价" + (list[i].price - list[i].realPrice) + "元");} else {tr = tr.replace(/#{msg}/g, "");}$("#cart-list").append(tr);}},error: function (xhr) {alert("加载购物车列表数据时产生未知的异常"+xhr.status);}});}
</script>
4.补充知识点
1.当从url中拿数据传递给后端时,在ajax请求里的data可以用下面的方式:
data:location.search.substring(1),// URL 中的 ? 及之后的部分,1就是从下标为1的开始,相当于把?去掉了
相关文章:
Springboot写电商系统(2)
Springboot写电商系统(2) 1.新增收货地址1.创建t_addresss数据库表2.创建Address实体类3.数据库操作的持久层1.接口写抽象方法2.xml写方法映射sql3.测试 4.前后数据交互的业务层1.sql操作的异常抛出2.交互方法的接口定义3.接口的方法实现4.测试 5.与前端…...
SpringBoot中过滤器与拦截器的区别
SpringBoot中过滤器与拦截器的区别 过滤器和拦截器的区别: ①拦截器是基于java的反射机制的,而过滤器是基于函数回调。 ②拦截器不依赖与servlet容器,过滤器依赖与servlet容器。 ③拦截器只能对action请求起作用,而过滤器则可以对…...
SystemVerilog(2)——数据类型
一、概述 和Verilog相比,SV提供了很多改进的数据结构。它们具有如下的优点: 双状态数据类型:更好的性能,更低的内存消耗队列、动态和关联数组:减少内存消耗,自带搜索和分类功能类和结构:支持抽…...
记一次Postgresql从堆叠注入到RCE
本次研究过程来自一次某cms的代码审计实战,整个环境部署的相对较好,postgresql、web权限都有单独的用户管理,web目录不可写、服务器不能出网等限制。不过比较幸运的是所有的数据操作都是用同一个superuser权限的postgresql用户来执行的。 限…...
通用FIFO设计深度8宽度64,verilog仿真,源码和视频
名称:通用FIFO设计深度8宽度64,verilog仿真 软件:Quartus 语言:verilog 本代码为FIFO通用代码,其他深度和位宽可简单修改以下参数得到 reg [63:0] ram [7:0];//RAM。深度8,宽度64 代码功能:…...
尝试进行表格处理
꧂ input输入多行文本,3个回车结束꧁ 用input输入如果你想要使用 input 输入多行文本,可以在输入时按照以下方式来终止输入: text while True:line input("请输入文本(按回车继续,按3个回车结束)…...
VueRouter 源码解析
重要函数思维导图 路由注册 在开始之前,推荐大家 clone 一份源码对照着看。因为篇幅较长,函数间的跳转也很多。 使用路由之前,需要调用 Vue.use(VueRouter),这是因为让插件可以使用 Vue export function initUse(Vue: GlobalAP…...
云原生之Docker
docker 初识Docker什么是DockerDocker与虚拟机Docker相关术语及架构镜像和容器DockerHubDocker架构 Docker命令镜像操作命令容器操作命令数据卷命令 自定义镜像镜像结构Dockerfile DockerCompose安装常用命令 初识Docker 什么是Docker docker是一个快速交付应用,运…...
List简介
概念: 数据结构列表(List)是Java中的一种线性数据结构,用于存储有序的元素集合。它允许重复元素,并且每个元素都有一个对应的索引来访问和操作。列表可以动态增长或缩小,并且支持添加、删除和修改操作。 …...
【ArcGIS Pro二次开发】(71):PPT文件操作方法汇总
以下操作都要用到【Microsoft.Office.Interop.PowerPoint】,确保安装并引用。 1、打开PPT文件 // 打开PPT Microsoft.Office.Interop.PowerPoint.Application pptApp new Microsoft.Office.Interop.PowerPoint.Application();Presentation ppt pptApp.Presentati…...
CloudCompare 二次开发(18)——法线空间采样
目录 一、概述二、代码集成三、结果展示一、概述 使用CloudCompare与PCL的混合编程实现点云法线空间采样。法线空间采样的具体计算原理见:PCL 法线空间采样。 二、代码集成 1、mainwindow.h文件public中添加: void doActionNormalSpaceSample(); // 法线空间采样2、mainwi…...
RFCN目标检测算法
...
【学习草稿】bert文本分类
https://github.com/google-research/bert https://github.com/CyberZHG/keras-bert 在 BERT 中,每个单词的嵌入向量由三部分组成: Token 嵌入向量:该向量是 WordPiece 分词算法得到的子单词 ID 对应的嵌入向量。 Segment 嵌入向量&#x…...
华为OD 食堂供餐(100分)【java】A卷+B卷
华为OD统一考试A卷+B卷 新题库说明 你收到的链接上面会标注A卷还是B卷。目前大部分收到的都是B卷。 B卷对应20022部分考题以及新出的题目,A卷对应的是新出的题目。 我将持续更新最新题目 获取更多免费题目可前往夸克网盘下载,请点击以下链接进入: 我用夸克网盘分享了「华为O…...
Hadoop3教程(二十七):(生产调优篇)HDFS读写压测
文章目录 (146)HDFS压测环境准备(147)HDFS读写压测写压测读压测 参考文献 (146)HDFS压测环境准备 对开发人员来讲,压测这个技能很重要。 假设你刚搭建好一个集群,就可以直接投入生…...
【MyBatis进阶】mybatis-config.xml分析以及try-catch新用法
目录 尝试在mybatis项目中书写增删改查 遇见问题:使用mybaties向数据库中插入数据,idea显示插入成功,但是数据库中并没有数据变化? MyBatis核心配置文件剖析 细节剖析: try-catch新用法 截至目前我的项目存在的问题…...
机器学习终极指南:统计和统计建模03/3 — 第 -3 部分
系列上文:机器学习终极指南:特征工程(02/2) — 第 -2 部分 一、说明 在终极机器学习指南的第三部分中,我们将了解统计建模的基础知识以及如何在 Python 中实现它们,Python 是一种广泛用于数据分析和科学计…...
php获取农历日期节日
代码地址:php获取农历日期节日-遇见你与你分享 <?php $c new DayService(); $today$c->convertSolarToLunar(date(Y),date(m),date(d)); $time "农历".$today[1].$today[2]."日";class DayService {var $MIN_YEAR 1891;var $MAX_YEAR …...
主机重启后k8s kubelet无法自动启动问题解决梳理
1.问题描述 OS Version:CentOS Linux release 7.9.2009 (Core) K8S Version:Kubernetes v1.20.4 K8S安装配置完成后,重启服务器发现,kubelet没有正常启动(systemctl status kubelet) 命令: systemctl status kubelet [root@centos79-3 ~]# systemctl status kubelet ●…...
Hadoop面试题(2)
1.什么是数据倾斜?如何处理数据倾斜? 数据倾斜指的是在分布式计算中,数据在某些节点上不均匀地分布,导致某些节点的负载过重,影响整体计算性能。 处理数据倾斜的方法主要包括以下几种: 增加分区数量&…...
使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...
深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...
五年级数学知识边界总结思考-下册
目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解:由来、作用与意义**一、知识点核心内容****二、知识点的由来:从生活实践到数学抽象****三、知识的作用:解决实际问题的工具****四、学习的意义:培养核心素养…...
C++.OpenGL (10/64)基础光照(Basic Lighting)
基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...
Unit 1 深度强化学习简介
Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库,例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体,比如 SnowballFight、Huggy the Do…...
嵌入式常见 CPU 架构
架构类型架构厂商芯片厂商典型芯片特点与应用场景PICRISC (8/16 位)MicrochipMicrochipPIC16F877A、PIC18F4550简化指令集,单周期执行;低功耗、CIP 独立外设;用于家电、小电机控制、安防面板等嵌入式场景8051CISC (8 位)Intel(原始…...
阿里云Ubuntu 22.04 64位搭建Flask流程(亲测)
cd /home 进入home盘 安装虚拟环境: 1、安装virtualenv pip install virtualenv 2.创建新的虚拟环境: virtualenv myenv 3、激活虚拟环境(激活环境可以在当前环境下安装包) source myenv/bin/activate 此时,终端…...
恶补电源:1.电桥
一、元器件的选择 搜索并选择电桥,再multisim中选择FWB,就有各种型号的电桥: 电桥是用来干嘛的呢? 它是一个由四个二极管搭成的“桥梁”形状的电路,用来把交流电(AC)变成直流电(DC)。…...
