此前已经完成“显示收货地址列表”功能,客户端(页面)向http://localhost:8080/addresses
发请求,就可以得到收货地址列表的数据!所以,只需要在orderConfirm.html页面中,当加载页面时,就直接向以上路径发请求,获取数据,并显示在下拉列表中即可:
<script type="text/javascript">
$(document).ready(function() {
showAddressList();
});
// orderConfirm.html: 第118行:增加id
function showAddressList() {
console.log("准备收货地址列表……");
$("#address-list").empty();
$.ajax({
"url":"/addresses",
"type":"get",
"dataType":"json",
"success":function(json) {
var list = json.data;
console.log("count=" + list.length);
for (var i = 0; i < list.length; i++) {
console.log(list[i].name);
var html = '';
html = html.replace(/#{aid}/g, list[i].aid);
html = html.replace(/#{tag}/g, list[i].tag);
html = html.replace(/#{name}/g, list[i].name);
html = html.replace(/#{province}/g, list[i].provinceName);
html = html.replace(/#{city}/g, list[i].cityName);
html = html.replace(/#{area}/g, list[i].areaName);
html = html.replace(/#{address}/g, list[i].address);
html = html.replace(/#{phone}/g, list[i].phone);
$("#address-list").append(html);
}
}
});
}
</script>
在购物车列表页,用户可以自由选择若干条数据,点击“结算”时,页面会将用户勾选的数据id传递到确认订单页,在确认订单页中,就需要根据这些id来显示对应的数据,所以,需要实现”根据若干个id查询购物车列表“的功能,需要执行的SQL语句与此前完成的“查询某用户的购物车列表”是高度相似的,只是查询条件要改为“根据若干个id查询”,则SQL语句大致是:
select
cid, uid, pid, t_cart.num, t_cart.price,
title, t_product.price AS realPrice, image
from
t_cart
left join
t_product
on
t_cart.pid=t_product.id
where
cid in (?,?,? ... ?)
order by
t_cart.created_time desc
在CartMapper
中添加:
/**
* 查询若干个数据id匹配的购物车列表
* @param cids 若干个数据id
* @return 匹配的购物车列表
*/
List<CartVO> findByCids(Integer[] cids); // List / Integer[] / Integer...
映射:
<select id="findByCids" resultType="cn.tedu.store.vo.CartVO">
SELECT
cid, uid, pid, t_cart.num, t_cart.price,
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=","
open="(" close=")">
#{cid}
foreach>
ORDER BY
t_cart.created_time DESC
select>
测试:
@Test
public void findByCids() {
Integer[] cids = { 10, 8, 14, 12, 6, 15, 16, 18, 20 };
List<CartVO> list = mapper.findByCids(cids);
System.err.println("count=" + list.size());
for (CartVO item : list) {
System.err.println(item);
}
}
无
在CartService
添加抽象方法:
/**
* 查询若干个数据id匹配的购物车列表
* @param cids 若干个数据id
* @param uid 用户的id
* @return 匹配的购物车列表
*/
List<CartVO> getByCids(Integer[] cids, Integer uid);
在CartServiceImpl
中,先将持久层新添加的抽象方法复制过来,改成私有方法并实现:
/**
* 查询若干个数据id匹配的购物车列表
* @param cids 若干个数据id
* @return 匹配的购物车列表
*/
private List<CartVO> findByCids(Integer[] cids) {
return cartMapper.findByCids(cids);
}
然后,规划重写接口中的抽象方法:
@Override
public List<CartVO> getByCids(Integer[] cids, Integer uid) {
// 调用findByCids(cids)执行查询,得到列表数据
List<CartVO> carts = findByCids(cids);
// 从列表中移除非当前登录用户的数据:在遍历过程中移除集合中的元素,需要使用迭代器
Iterator<CartVO> it = carts.iterator();
while (it.hasNext()) {
CartVO cart = it.next();
if (!cart.getUid().equals(uid)) {
it.remove();
}
}
// 返回列表
return carts;
}
最后,测试:
@Test
public void findByCids() {
Integer[] cids = { 10, 8, 14, 12, 6, 15, 16, 18, 20 };
Integer uid = 13;
List<CartVO> list = service.getByCids(cids, uid);
System.err.println("count=" + list.size());
for (CartVO item : list) {
System.err.println(item);
}
}
无
/carts/get_by_cids
Integer[] cids
, HttpSession session
GET
JsonResult>
在CartController
中处理请求:
// http://localhost:8080/carts/get_by_cids?cids=6&cids=8&cids=15&cids=16
@GetMapping("get_by_cids")
public JsonResult<List<CartVO>> getByCids(Integer[] cids, HttpSession session) {
Integer uid = getUidFromSession(session);
List<CartVO> data = cartService.getByCids(cids, uid);
return new JsonResult<>(OK, data);
}
通过http://localhost:8080/carts/get_by_cids?cids=6&cids=8&cids=15&cids=16
function showCartList(){
$("#cart-list").empty();
$.ajax({
"url":"/carts/get_by_cids",
"data":location.search.substr(1),
"type":"get",
"dateType":"json",
"success":function(json){
var arr = json.date;
for (var i = 0; i < arr.length; i++) {
var html = ''
+ ' '
+ '#{title} '
+ '¥#{realPrice} '
+ '#{num} '
+ '#{totalPrice} '
+ ' ';
console.log(arr[i].cid);
// 使用正则全文匹配
html = html.replace(/#{image}/g, arr[i].image);
html = html.replace(/#{cid}/g, arr[i].cid);
html = html.replace(/#{title}/g, arr[i].title);
html = html.replace(/#{realPrice}/g, arr[i].realPrice);
html = html.replace(/#{num}/g, arr[i].num);
html = html.replace(/#{totalPrice}/g, arr[i].realPrice * arr[i].num);
$("#cart-list").append(html);
}
}
});
}
创建订单表
CREATE TABLE t_order (
oid INT AUTO_INCREMENT COMMENT '订单id',
uid INT COMMENT '归属用户的id',
recv_name VARCHAR(20) 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 '收货详细地址',
order_time DATETIME COMMENT '下单时间',
pay_time DATETIME COMMENT '支付时间',
total_price BIGINT COMMENT '支付金额',
status INT(1) COMMENT '订单状态:0-未支付,1-已支付,2-已取消,3-已关闭',
created_user VARCHAR(20) COMMENT '创建人',
created_time DATETIME COMMENT '创建时间',
modified_user VARCHAR(20) COMMENT '最后修改人',
modified_time DATETIME COMMENT '最后修改时间',
PRIMARY KEY (oid)
) DEFAULT CHARSET=utf8mb4;
创建订单商品表
CREATE TABLE t_order_item (
id INT AUTO_INCREMENT COMMENT 'id',
oid INT COMMENT '归属订单',
pid INT COMMENT '商品id',
title VARCHAR(100) 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)
) DEFAULT CHARSET=utf8mb4;
订单的实体类:
/**
* 订单数据的实体类
*/
public class Order extends BaseEntity {
private static final long serialVersionUID = -3216224344757796927L;
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 Date orderTime;
private Date payTime;
private Long totalPrice;
private Integer status;
}
订单商品的实体类:
/**
* 订单商品实体类
*/
public class OrderItem extends BaseEntity {
private static final long serialVersionUID = -8879247924788259070L;
private Integer id;
private Integer oid;
private Integer pid;
private String title;
private String image;
private Long price;
private Integer num;
}
在cn.demo.store.mapper
中创建OrderMapper
接口,添加抽象方法:
/**
* 处理订单数据和订单商品数据的持久层接口
*/
public interface OrderMapper {
/**
* 插入订单数据
* @param order 订单数据
* @return 受影响的行数
*/
Integer insertOrder(Order order);
/**
* 插入订单商品数据
* @param orderItem 订单商品数据
* @return 受影响的行数
*/
Integer insertOrderItem(OrderItem orderItem);
}
在src/main/resources/mappers下复制粘贴得到OrderMapper.xml文件,配置以上抽象方法的映射:
<mapper namespace="cn.demo.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, order_time,
pay_time, total_price, status,
created_user, created_time, modified_user, modified_time
) VALUES (
#{uid}, #{recvName}, #{recvPhone}, #{recvProvince},
#{recvCity}, #{recvArea}, #{recvAddress}, #{orderTime},
#{payTime}, #{totalPrice}, #{status},
#{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>
最后,创建测试类,并测试以上方法:
@RunWith(SpringRunner.class)
@SpringBootTest
public class OrderMapperTests {
@Autowired
private OrderMapper mapper;
@Test
public void insertOrder() {
Order order = new Order();
order.setUid(1);
Integer rows = mapper.insertOrder(order);
System.err.println("rows=" + rows);
System.err.println(order);
}
@Test
public void insertOrderItem() {
OrderItem orderItem = new OrderItem();
orderItem.setOid(1);
Integer rows = mapper.insertOrderItem(orderItem);
System.err.println("rows=" + rows);
System.err.println(orderItem);
}
}
在本次处理过程中,需要根据收货地址id查询收货地址详情,由于是在Service层调用的,应该在AddressService
中定义相关的方法,以便于调用!
所以,先在AddressService
中添加抽象方法:
/**
* 根据收货地址id查询收货地址详情
* @param aid 收货地址id
* @return 匹配的收货地址详情,如果没有匹配的数据,则返回null
*/
Address getByAid(Integer aid);
然后,在AddressServiceImpl
中实现以上方法:
@Override
public Address getByAid(Integer aid) {
// 调用私有的findByAid()找出数据
Address result = findByAid(aid);
// 判断查询结果是否为null
if (result == null) {
// 是:AddressNotFoundException
throw new AddressNotFoundException("获取收货地址数据失败!尝试访问的数据不存在!");
}
// 将不需要对外提供的属性值设置为null
result.setProvinceCode(null);
result.setCityCode(null);
result.setAreaCode(null);
result.setIsDefault(null);
result.setCreatedUser(null);
result.setCreatedTime(null);
result.setModifiedUser(null);
result.setModifiedTime(null);
// 返回数据
return result;
}
创建OrderService
接口,在接口中添加抽象方法:
Order createOrder(Integer aid, Integer[] cids, Integer uid, String username);
创建OrderServiceImpl
类,实现以上接口,添加@Service
注解,添加@Autowired private OrderMapper orderMapper;
持久层对象、@Autowired private AddressService addressService;
处理收货地址的业务对象、@Autowired private CartService cartService;
处理购物车数据的业务对象:
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private AddressService addressService;
@Autowired
private CartService cartService;
}
然后,将持久层的2个方法复制到当前类中,改为私有方法并实现:
/**
* 插入订单数据
* @param order 订单数据
*/
private void insertOrder(Order order) {
Integer rows = orderMapper.insertOrder(order);
if (rows != 1) {
throw new InsertException("创建订单失败!插入订单数据时出现未知错误,请联系系统管理员!");
}
}
/**
* 插入订单商品数据
* @param orderItem 订单商品数据
*/
private void insertOrderItem(OrderItem orderItem) {
Integer rows = orderMapper.insertOrderItem(orderItem);
if (rows != 1) {
throw new InsertException("创建订单失败!插入订单商品数据时出现未知错误,请联系系统管理员!");
}
}
然后,规划如何重写接口中的抽象方法:
@Override
@Transactional
public Order createOrder(Integer aid, Integer[] cids, Integer uid, String username) {
// 创建当前时间对象now
Date now = new Date();
// 根据参数aid调用addressService.getByAid()查询收货地址详情
Address address = addressService.getByAid(aid);
// 根据参数cids调用cartService.getByCids(),得到List
List<CartVO> carts = cartService.getByCids(cids, uid);
// 定义totalPrice变量
Long totalPrice = 0L;
// 遍历以上查询到的List,计算出totalPrice
for (CartVO cart : carts) {
totalPrice += cart.getRealPrice() * cart.getNum();
}
// 创建Order对象
Order order = new Order();
// 补全Order对象中的属性:uid > 参数uid
order.setUid(uid);
// 补全Order对象中的属性:recv* > 收货地址详情
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对象中的属性:orderTime > now
order.setOrderTime(now);
// 补全Order对象中的属性:payTime > null
// 补全Order对象中的属性:totalPrice > totalPrice
order.setTotalPrice(totalPrice);
// 补全Order对象中的属性:status > 0
order.setStatus(0);
// 补全Order对象中的属性:4个日志
order.setCreatedUser(username);
order.setCreatedTime(now);
order.setModifiedUser(username);
order.setModifiedTime(now);
// 调用insertOrder(Order order)插入订单数据
insertOrder(order);
// 遍历查询到的List
for (CartVO cart : carts) {
// -- 创建OrderItem对象
OrderItem orderItem = new OrderItem();
// -- 补全OrderItem对象中的属性:oid > order.getOid();
orderItem.setOid(order.getOid());
// -- 补全OrderItem对象中的属性:pid, title, image, price, num > CartVO对象中的属性
orderItem.setPid(cart.getPid());
orderItem.setTitle(cart.getTitle());
orderItem.setImage(cart.getImage());
orderItem.setPrice(cart.getRealPrice());
orderItem.setNum(cart.getNum());
// -- 补全OrderItem对象中的属性:4个日志
orderItem.setCreatedUser(username);
orderItem.setCreatedTime(now);
orderItem.setModifiedUser(username);
orderItem.setModifiedTime(now);
// -- 多次调用insertOrderItem(OrderItem orderItem)插入订单商品数据
insertOrderItem(orderItem);
}
// 返回订单数据
Order result = new Order();
result.setOid(order.getOid());
result.setTotalPrice(order.getTotalPrice());
return result;
}
完成后,创建测试类,并测试:
@RunWith(SpringRunner.class)
@SpringBootTest
public class OrderServiceTests {
@Autowired
private OrderService service;
@Test
public void createOrder() {
try {
Integer aid = 28;
Integer[] cids = { 5, 10, 14, 15 };
Integer uid = 12;
String username = "HAHA";
service.createOrder(aid, cids, uid, username);
System.err.println("OK.");
} catch (ServiceException e) {
System.err.println(e.getClass().getName());
System.err.println(e.getMessage());
}
}
}
关于需要处理的请求:
/orders/create
Integer aid
, Integer[] cids
, HttpSession session
POST
JsonResult
创建控制器类,并处理请求:
@RestController
@RequestMapping("orders")
public class OrderController extends BaseController {
@Autowired
private OrderService orderService;
// http://localhost:8080/orders/create?aid=28&cids=12&cids=8&cids=10
@PostMapping("create")
public JsonResult<Order> create(Integer aid, Integer[] cids, HttpSession session) {
Integer uid = getUidFromSession(session);
String username = getUsernameFromSession(session);
Order data = orderService.createOrder(aid, cids, uid, username);
return new JsonResult<>(OK, data);
}
}
$("#btn-create-order").click(function(){
// 前端对数据进行校验
$.ajax({
"url":"/orders/create",
"data":$("#form-create-order").serialize(),
"type":"post",
"dateType":"json",
"success":function(json){
if(json.state==200){
alert("创建订单成功!订单号是:");
}else{
alert(json.message);
}
},
"error":function(){
alert("您的登录信息已过期,请重新登录!");
}
});
});
在持久层,并没有删除购物车中的数据的功能,由于具体操作是购物车数据,所以,应该由CartMapper
负责完成!所以,在CartMapper
接口中添加:
Integer deleteByCids(@Param("cids") Integer[] cids, @Param("uid") Integer uid);
配置的映射:
<!-- 删除某用户的若干个购物车数据 -->
<!-- Integer deleteByCids(
@Param("cids") Integer[] cids,
@Param("uid") Integer uid
); -->
<delete id="deleteByCids">
DELETE FROM
t_cart
WHERE
uid=#{uid} AND cid IN
<foreach collection="cids" item="cid" separator=","
open="(" close=")">
#{cid}
</foreach>
</delete>
关于测试:
@Test
public void deleteByCids() {
Integer uid = 12;
Integer[] cids = { 5, 7, 9 };
Integer rows = mapper.deleteByCids(cids, uid);
System.err.println("rows=" + rows);
}
然后,还需要在处理购物车的业务层提供该功能,所以,在CartService
中添加:
void delete(Integer[] cids, Integer uid);
在CartServiceImpl
中,先将持久层新增的方法复制过来,改为私有方法:
/**
* 删除某用户的若干个购物车数据
* @param cids 被删除的购物车数据的id
* @param uid 用户的id
*/
private void deleteByCids(Integer[] cids, Integer uid) {
Integer rows = cartMapper.deleteByCids(cids, uid);
if (rows < 1) {
throw new DeleteException("删除购物车数据失败!删除购物车数据时出现未知错误,请联系系统管理员!");
}
}
然后重写业务接口中的抽象方法:
public void delete(Integer[] cids, Integer uid) {
deleteByCids(cids, uid);
}
完成后,测试:
@Test
public void delete() {
Integer uid = 12;
Integer[] cids = { 6, 8 };
service.delete(cids, uid);
System.err.println("OK.");
}
在OrderServiceImpl
中添加,调用其删除购物车方法即可
// 创建订单后删除购物车中的记录
cartService.delete(cids, uid);
// 将库存做减法操作
销库存的操作本质是减少商品表中的库存值,是关于商品数据的操作,所以,应该在处理商品数据的持久层和业务层来完成对应的操作。
先在ProductMapper
中添加“更新商品库存值”的抽象方法,其对应的SQL语句是:
update t_product set num=?, modified_user=?, modified_time=? where id=?
所以,可以将抽象方法设计为:
/**
* 更新商品的库存
* @param id 商品的id
* @param num 新的库存值
* @param modifiedUser 最后修改人
* @param modifiedTime 最后修改时间
* @return 受影响的行数
*/
Integer updateNumById(
@Param("id") Integer id,
@Param("num") Integer num,
@Param("modifiedUser") String modifiedUser,
@Param("modifiedTime") Date modifiedTime
);
然后,在ProductMapper.xml中配置映射:
<update id="updateNumById">
UPDATE
t_product
SET
num=#{num},
modified_user=#{modifiedUser},
modified_time=#{modifiedTime}
WHERE
id=#{id}
update>
完成后,在ProductMapperTests
中编写并执行单元测试:
@Test
public void updateNumById() {
Integer id = 10000001;
Integer num = 5;
String modifiedUser = "系统管理员";
Date modifiedTime = new Date();
Integer rows = mapper.updateNumById(id, num, modifiedUser, modifiedTime);
System.err.println("rows=" + rows);
}
接下来,应该完成业务层的开发,首先,应该创建ProductOutOfStockException
异常,用于表示“商品库存不足”。
然后,在业务层接口ProductService
中添加抽象方法:
/**
* 减少商品的库存
* @param id 商品的id
* @param amount 减少的数量
* @param username 用户名
*/
void reduceNum(Integer id, Integer amount, String username);
在业务层实现类ProductServiceImpl
中,先将持久层的抽象方法复制过来,改为私有方法,并实现:
/**
* 更新商品的库存
* @param id 商品的id
* @param num 新的库存值
* @param modifiedUser 最后修改人
* @param modifiedTime 最后修改时间
*/
private void updateNumById(Integer id, Integer num, String modifiedUser, Date modifiedTime) {
Integer rows = productMapper.updateNumById(id, num, modifiedUser, modifiedTime);
if (rows != 1) {
throw new UpdateException("更新商品库存失败!更新库存值时出现未知错误,请联系系统管理员!");
}
}
然后,设计重写接口中的抽象方法:
@Override
public void reduceNum(Integer id, Integer amount, String username) {
// 基于参数id调用findById()查询商品数据
Product result = findById(id);
// 判断查询结果是否为null
if (result == null) {
// 是:ProductNotFoundException
throw new ProductNotFoundException("减少商品库存失败!尝试访问的商品数据不存在!");
}
// 通过查询结果可以得到原库存值,结合参数amount,计算得到新的库存值
Integer num = result.getNum() - amount;
// 判断新的库存值是否<0
if (num < 0) {
// 是:抛出ProductOutOfStockException
throw new ProductOutOfStockException("更新商品库存失败!商品库存不足!");
}
// 调用updateNumById()更新商品的库存
updateNumById(id, num, username, new Date());
}
完成后,在ProductServiceTests
中测试:
@Test
public void reduceNum() {
try {
Integer id = 10000001;
Integer amount = 5;
String username = "ADMIN";
service.reduceNum(id, amount, username);
System.err.println("OK.");
} catch (ServiceException e) {
System.err.println(e.getClass().getName());
System.err.println(e.getMessage());
}
}
最后,在GlobalHandleException
类中添加处理ProductOutOfStockException
异常。
至此,处理商品数据的持久层和业务层已经完成所需要负责的功能,只需要创建订单过程中添加“销库存”的操作即可。
首先,如果只是解决“归还库存”的功能,其数据操作与以上“销库存”是极为相似的,代码的开发难度也不高。该操作比较纠结的是“如何归还”,也就是通过什么技术手段来实现“创建订单后,15分钟(或其它)后未支付则关闭订单并归还库存”。
关于功能的实现,可以通过线程来操作,例如,自定义一个线程,大概是:
public class XxxThread extends Thread {
private Integer pid;
private Integer amount;
public XxxThread(Integer pid, Integer amount) {
this.pid = pid;
this.amount = amount;
}
@Override
public void run() {
try {
// 休眠15分钟
Thread.sleep(15 * 60 * 1000);
} catch (Exception e) {
}
// 判断订单状态,如果状态仍为0,表示未支付,则归还库存
}
}
然后,每次创建订单时,都启动一个这样的线程即可!
当然,如果不使用线程,使用其它相关类似的做法也是可以的,例如定时器等。
这种做法是非常直接且有效的做法!但是,由于每个线程的执行时间都是15分钟以上,在大型电商平台中,15分钟内产生的订单量可能非常大,而每个订单对应1个线程,就会导致服务器需要创建大量的线程对象,并在在内存中存在大量的线程对象!甚至,服务器根本不足以支撑实现!
以上做法不可行时,就需要考虑其它的解决方案,可行的解决方案还可以是:
public class XxxThread extends Thread {
private boolean isRunning;
public void setIsRunning(boolean isRunning) {
this.isRunning = isRunning;
}
@Override
public void run() {
isRunning = true;
while (isRunning) {
try {
// 休眠10秒钟
Thread.sleep(10 * 1000);
} catch (Exception e) {
}
// 查找订单表中所有status=0并且order_time达到15分钟或以上的数据
// 将这些订单数据的status改为“已关闭”,并基于这些订单数据归还库存
}
}
}
使用这种做法的缺陷在于时效性可能不够精准,以上代码中休眠的时间越长,误差就会越大!但是,由于时效性不精准导致的误差,最大的问题就是“订单刚刚超时的那一段时间之前,用户仍可以支付该订单”,这个问题作为电商平台是可以接受的,所以,可以不当成实际需要解决的问题。
首先,客户端应该对所有需要提交到服务器的数据进行基本检查,例如字符串的长度、字符串的组成、字符串的格式、数值的大小等等,如果任何一个数据检查不通过,都不应该提交请求到服务器。
然后,在服务器端,在控制器中,收到请求参数的第一时间,也应该做同样的检查,因为客户端提交的数据都应该视为不可靠,在大型应用中,客户端的种类可能比较多,如果某些数据格式有所调整,并不一定是所有客户端都使用了新的规则,甚至有些客户端版本没有更新,则会导致一些没有通过新规则检查的数据被提交到服务器,甚至,客户端软件是安装在用户的设备的,存在被破解、篡改的可能,所以,客户端提交的所有数据都应该视为不可靠数据,都必须检查!
需要注意的是:即使在服务器的控制器中有完整的数据格式检查,客户端的检查依然是必须存在的,这样的阻止大量的错误格式数据的请求,从而减轻服务器的压力!
所以,客户端检查的目的是为了拦截绝大部分格式本身就有问题的数据,当数据本身有问题时,并不向服务器发送请求,以减轻服务器压力,而服务器端的控制器检查的目的才是真正确保数据格式无误的!
在一些大型应用中,甚至业务层(Service)也可能会对各参数进行检查!在普通的数据处理中,数据提交到服务器后,会由控制器接收,控制器对这些数据进行检查后,再调用业务层进行下一步的处理,此时,业务层得到的数据参数其实都是控制器已经检查过来,都是格式无误的数据,可以不必检查,但是,不排除某些业务方法并不是由控制器调用的,例如一些计划任务等,它们是服务器内部自发执行,可能某些参数来自数据库、配置信息、程序运行过程中产生的值,所以,这些值并不一定是安全有效的值,也应该在执行业务方法之前,对这些值进行基本检查。
所以,关于参数的检查主要体现在: