接口幂等性
同一个用户多次请求和一次请求对结果的影响是一样的这就叫幂等性
解决方案:创建一个防重表
CREATE TABLE `anti_repeat` (
`id` int(11) NOT NULL,
`msg` varchar(255) DEFAULT NULL COMMENT '描述信息',
`create_date` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
创建一个用户表模拟
CREATE TABLE `t_user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
`userName` varchar(255) DEFAULT NULL COMMENT '用户名',
`money` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8
dao
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yujie.model.AntiRepeat;
public interface AntiRepeatDao extends BaseMapper {
}
----------------------------------------------------------------
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yujie.model.User;
public interface UserDao extends BaseMapper{
}
model
import com.baomidou.mybatisplus.annotation.TableName;
import java.util.Date;
@TableName(value="anti_repeat")
public class AntiRepeat {
private Integer id;
private String msg;
private Date createDate;
public AntiRepeat(Integer id, String msg) {
this.id = id;
this.msg = msg;
}
public AntiRepeat(Integer id, String msg, Date createDate) {
this.id = id;
this.msg = msg;
this.createDate = createDate;
}
get...
set...
}
--------------------------------------------------------------------
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
@TableName(value = "t_user")
public class User implements Serializable {
private static final long serialVersionUID = -2929599080614903612L;
private Integer id;
private String username;
private Integer money;
get...
set...
}
Controller
@RestController
@RequestMapping("/thread")
public class ThreadController {
@Autowired
private UserService userService;
@RequestMapping("/subtraction")
public JsonResult subtraction(String username,int money, int id){
JsonResult jsonResult= new JsonResult();
try {
jsonResult = userService.subtraction(username, money, id);
}catch (Exception e){
System.out.println("请勿重复提交 ms:"+new Date().getTime());
jsonResult.setMsg("请勿重复提交");
jsonResult.setCode(500);
}
return jsonResult;
}
}
Service
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserDao userDao;
@Autowired
private AntiRepeatDao antiRepeatDao;
@Transactional
@Override
public JsonResult subtraction(String username, int money, int id) {
AntiRepeat antiRepeat = antiRepeatDao.selectById(id);
if(antiRepeat != null){
throw new RuntimeException("请勿重复提交");
}
LambdaQueryWrapper lqw = new LambdaQueryWrapper();
lqw.eq(User::getUsername,username);
User user = userDao.selectOne(lqw);
user.setMoney(user.getMoney()-money);
int count = userDao.updateById(user);
JsonResult jsonResult = new JsonResult();
String str="用户:"+username+"扣减"+money+"金额";
if(count == 0){
jsonResult.setMsg(str+"失败");
jsonResult.setCode(500);
return jsonResult;
}
AntiRepeat antiRepeat1 = new AntiRepeat(id,str+"成功",new Date());
antiRepeatDao.insert(antiRepeat1);
System.out.println(str+"成功 ms:"+new Date().getTime());
return jsonResult;
}
}
测试链接http://localhost:8080/thread/subtraction?username=a&money=10&id=10086
使用JMeter进行并发100个线程测试
用户:a扣减10金额成功 ms:1675186191711
请勿重复提交 ms:1675186191717
请勿重复提交 ms:1675186191719
请勿重复提交 ms:1675186191721
请勿重复提交 ms:1675186191723
请勿重复提交 ms:1675186191725
请勿重复提交 ms:1675186191726
请勿重复提交 ms:1675186191728
请勿重复提交 ms:1675186191729
请勿重复提交 ms:1675186191733
用户表可以看到a用户从原来的100块钱成功减少到90
防重表可以看到多了一条id为10086的记录
总结以上就是保证了接口幂等性,流程:携带用户名和金额以及随机的id发送给后端,
后端防重表进行存储随机id,这个id的生成方式很重要,
同一个页面多次点击前端生成同一个id,只有服务器返回结果后才能改变id,
否则当前页面的id永不变
缺点就是用户点击充值按钮服务器还没给用户响应,用户把页面关闭此时存储的还是老id值,下次用户在打开浏览器充值就会发现提示重复提交 ,此时返回重复提交状态应该重定向到首页面刷新id让用户在点一次充值按钮,我们也应该这么做返回重复提交提示就重定向到首页如果用户想充第二次就必须在点一次充值按钮,第二种方式可以打开浏览器的时候随机id也能解决