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.什么是数据倾斜?如何处理数据倾斜? 数据倾斜指的是在分布式计算中,数据在某些节点上不均匀地分布,导致某些节点的负载过重,影响整体计算性能。 处理数据倾斜的方法主要包括以下几种: 增加分区数量&…...

利用最小二乘法找圆心和半径
#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...

智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...
PHP和Node.js哪个更爽?
先说结论,rust完胜。 php:laravel,swoole,webman,最开始在苏宁的时候写了几年php,当时觉得php真的是世界上最好的语言,因为当初活在舒适圈里,不愿意跳出来,就好比当初活在…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...

让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...
Java数值运算常见陷阱与规避方法
整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...

MFC 抛体运动模拟:常见问题解决与界面美化
在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...

【C++进阶篇】智能指针
C内存管理终极指南:智能指针从入门到源码剖析 一. 智能指针1.1 auto_ptr1.2 unique_ptr1.3 shared_ptr1.4 make_shared 二. 原理三. shared_ptr循环引用问题三. 线程安全问题四. 内存泄漏4.1 什么是内存泄漏4.2 危害4.3 避免内存泄漏 五. 最后 一. 智能指针 智能指…...

Python 实现 Web 静态服务器(HTTP 协议)
目录 一、在本地启动 HTTP 服务器1. Windows 下安装 node.js1)下载安装包2)配置环境变量3)安装镜像4)node.js 的常用命令 2. 安装 http-server 服务3. 使用 http-server 开启服务1)使用 http-server2)详解 …...