司机接单
需求
- 乘客下单之后,新订单信息已经在司机临时队列
- 下面司机可以开始进行接单了
首先,司机登录,认证(身份证、驾驶证、创建人脸模型)
第二,司机进行人脸识别(每天司机接单之前都需要进行人脸识别)
第三,司机开始接单了,更新司机接单状态
第四,当司机开始接单之后,删除司机之前存储到Redis里面位置信息
第五,当司机开始接单之后,清空司机临时队列新订单信息
查找司机端当前订单
后续完善,目前默认司机当前没有正在执行订单
@Operation(summary = "查找司机端当前订单")
@GuiguLogin
@GetMapping("/searchDriverCurrentOrder")
public Result<CurrentOrderInfoVo> searchDriverCurrentOrder() {
CurrentOrderInfoVo currentOrderInfoVo = new CurrentOrderInfoVo();
currentOrderInfoVo.setIsHasCurrentOrder(false);
return Result.ok(currentOrderInfoVo);
}
判断司机在当日是否人脸识别
@Operation(summary = "判断司机当日是否进行过人脸识别")
@GetMapping("/isFaceRecognition/{driverId}")
Result<Boolean> isFaceRecognition(@PathVariable("driverId") Long driverId) {
return Result.ok(driverInfoService.isFaceRecognition(driverId));
}
判断司机当日是否进行过人脸识别
@Override
public Boolean isFaceRecognition(Long driverId) {
//根据司机id + 当日日期进行查询
LambdaQueryWrapper<DriverFaceRecognition> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(DriverFaceRecognition::getDriverId,driverId);
// 年-月-日 格式
wrapper.eq(DriverFaceRecognition::getFaceDate,new DateTime().toString("yyyy-MM-dd"));
//调用mapper方法
Long count = driverFaceRecognitionMapper.selectCount(wrapper);
return count != 0;
}
/**
* 判断司机当日是否进行过人脸识别
* @param driverId
* @return
*/
@GetMapping("/driver/info/isFaceRecognition/{driverId}")
Result<Boolean> isFaceRecognition(@PathVariable("driverId") Long driverId);
@Operation(summary = "判断司机当日是否进行过人脸识别")
@GuiguLogin
@GetMapping("/isFaceRecognition")
Result<Boolean> isFaceRecognition() {
Long driverId = AuthContextHolder.getUserId();
return Result.ok(driverService.isFaceRecognition(driverId));
}
@Override
public Boolean isFaceRecognition(Long driverId) {
return driverInfoFeignClient.isFaceRecognition(driverId).getData();
}
人脸识别接口
-
进行人脸识别,基于腾讯云实现
-
之前创建人脸识别模型,基于之前创建人脸模型完成当前识别功能
-
找到腾讯云文档1:照片比对
https://console.cloud.tencent.com/api/explorer?Product=iai&Version=2020-03-03&Action=VerifyFace -
找到腾讯云文档2:人脸静态活体检测
https://console.cloud.tencent.com/api/explorer?Product=iai&Version=2020-03-03&Action=DetectLiveFace
@Operation(summary = "验证司机人脸")
@PostMapping("/verifyDriverFace")
public Result<Boolean> verifyDriverFace(@RequestBody DriverFaceModelForm driverFaceModelForm) {
return Result.ok(driverInfoService.verifyDriverFace(driverFaceModelForm));
}
//人脸识别
@Override
public Boolean verifyDriverFace(DriverFaceModelForm driverFaceModelForm) {
//1 照片比对
try{
// 实例化一个认证对象,入参需要传入腾讯云账户 SecretId 和 SecretKey,此处还需注意密钥对的保密
// 代码泄露可能会导致 SecretId 和 SecretKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考,建议采用更安全的方式来使用密钥,请参见:https://cloud.tencent.com/document/product/1278/85305
// 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取
Credential cred = new Credential(tencentCloudProperties.getSecretId(),
tencentCloudProperties.getSecretKey());
// 实例化一个http选项,可选的,没有特殊需求可以跳过
HttpProfile httpProfile = new HttpProfile();
httpProfile.setEndpoint("iai.tencentcloudapi.com");
// 实例化一个client选项,可选的,没有特殊需求可以跳过
ClientProfile clientProfile = new ClientProfile();
clientProfile.setHttpProfile(httpProfile);
// 实例化要请求产品的client对象,clientProfile是可选的
IaiClient client = new IaiClient(cred,
tencentCloudProperties.getRegion(),
clientProfile);
// 实例化一个请求对象,每个接口都会对应一个request对象
VerifyFaceRequest req = new VerifyFaceRequest();
//设置相关参数
req.setImage(driverFaceModelForm.getImageBase64());
req.setPersonId(String.valueOf(driverFaceModelForm.getDriverId()));
// 返回的resp是一个VerifyFaceResponse的实例,与请求对象对应
VerifyFaceResponse resp = client.VerifyFace(req);
// 输出json格式的字符串回包
System.out.println(AbstractModel.toJsonString(resp));
if(resp.getIsMatch()) { //照片比对成功
//2 如果照片比对成功,静态活体检测
Boolean isSuccess = this.
detectLiveFace(driverFaceModelForm.getImageBase64());
if(isSuccess) {//3 如果静态活体检测通过,添加数据到认证表里面
DriverFaceRecognition driverFaceRecognition = new DriverFaceRecognition();
driverFaceRecognition.setDriverId(driverFaceModelForm.getDriverId());
driverFaceRecognition.setFaceDate(new Date());
driverFaceRecognitionMapper.insert(driverFaceRecognition);
return true;
}
}
} catch (TencentCloudSDKException e) {
System.out.println(e.toString());
}
throw new GuiguException(ResultCodeEnum.DATA_ERROR);
}
//人脸静态活体检测
private Boolean detectLiveFace(String imageBase64) {
try{
// 实例化一个认证对象,入参需要传入腾讯云账户 SecretId 和 SecretKey,此处还需注意密钥对的保密
// 代码泄露可能会导致 SecretId 和 SecretKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考,建议采用更安全的方式来使用密钥,请参见:https://cloud.tencent.com/document/product/1278/85305
// 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取
Credential cred = new Credential(tencentCloudProperties.getSecretId(),
tencentCloudProperties.getSecretKey());
// 实例化一个http选项,可选的,没有特殊需求可以跳过
HttpProfile httpProfile = new HttpProfile();
httpProfile.setEndpoint("iai.tencentcloudapi.com");
// 实例化一个client选项,可选的,没有特殊需求可以跳过
ClientProfile clientProfile = new ClientProfile();
clientProfile.setHttpProfile(httpProfile);
// 实例化要请求产品的client对象,clientProfile是可选的
IaiClient client = new IaiClient(cred, tencentCloudProperties.getRegion(),
clientProfile);
// 实例化一个请求对象,每个接口都会对应一个request对象
DetectLiveFaceRequest req = new DetectLiveFaceRequest();
req.setImage(imageBase64);
// 返回的resp是一个DetectLiveFaceResponse的实例,与请求对象对应
DetectLiveFaceResponse resp = client.DetectLiveFace(req);
// 输出json格式的字符串回包
System.out.println(DetectLiveFaceResponse.toJsonString(resp));
if(resp.getIsLiveness()) {
return true;
}
} catch (TencentCloudSDKException e) {
System.out.println(e.toString());
}
return false;
}
- 静态活体检测是一种技术,用于判别人脸是否为真实活体,而不是通过照片、视频或其他非真实的方式呈现的。
更新司机接单状态
@Operation(summary = "更新接单状态")
@GetMapping("/updateServiceStatus/{driverId}/{status}")
public Result<Boolean> updateServiceStatus(@PathVariable Long driverId, @PathVariable Integer status) {
return Result.ok(driverInfoService.updateServiceStatus(driverId, status));
}
Boolean updateServiceStatus(Long driverId, Integer status);
@Transactional
@Override
public Boolean updateServiceStatus(Long driverId, Integer status) {
LambdaQueryWrapper<DriverSet> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DriverSet::getDriverId, driverId);
DriverSet driverSet = new DriverSet();
driverSet.setServiceStatus(status);
driverSetMapper.update(driverSet, queryWrapper);
return true;
}
/**
* 更新接单状态
* @param driverId
* @param status
* @return
*/
@GetMapping("/driver/info/updateServiceStatus/{driverId}/{status}")
Result<Boolean> updateServiceStatus(@PathVariable("driverId") Long driverId, @PathVariable("status") Integer status);
开启和停止接单的web接口
司机要开启接单后,上传位置信息到redis的geo,这样才能被任务调度搜索到司机信息,才能抢单。
@Operation(summary = "开始接单服务")
@GuiguLogin
@GetMapping("/startService")
public Result<Boolean> startService() {
Long driverId = AuthContextHolder.getUserId();
return Result.ok(driverService.startService(driverId));
}
Boolean startService(Long driverId);
@Autowired
private LocationFeignClient locationFeignClient;
@Autowired
private NewOrderFeignClient newOrderDispatchFeignClient;
@Override
public Boolean startService(Long driverId) {
//判断认证状态
DriverLoginVo driverLoginVo = driverInfoFeignClient.getDriverLoginInfo(driverId).getData();
if(driverLoginVo.getAuthStatus().intValue() != 2) {
throw new GuiguException(ResultCodeEnum.AUTH_ERROR);
}
//判断当日是否人脸识别
Boolean isFaceRecognition = driverInfoFeignClient.isFaceRecognition(driverId).getData();
if(!isFaceRecognition) {
throw new GuiguException(ResultCodeEnum.FACE_ERROR);
}
//更新司机接单状态
driverInfoFeignClient.updateServiceStatus(driverId, 1);
//删除司机位置信息
locationFeignClient.removeDriverLocation(driverId);
//清空司机新订单队列
newOrderDispatchFeignClient.clearNewOrderQueueData(driverId);
return true;
}
司机抢成功单,就要关闭接单服务。
@Operation(summary = "停止接单服务")
@GuiguLogin
@GetMapping("/stopService")
public Result<Boolean> stopService() {
Long driverId = AuthContextHolder.getUserId();
return Result.ok(driverService.stopService(driverId));
}
Boolean stopService(Long driverId);
@Override
public Boolean stopService(Long driverId) {
//更新司机接单状态
driverInfoFeignClient.updateServiceStatus(driverId, 0);
//删除司机位置信息
locationFeignClient.removeDriverLocation(driverId);
//清空司机新订单队列
newOrderDispatchFeignClient.clearNewOrderQueueData(driverId);
return true;
}
司机抢单基本功能
当前司机已经开启接单服务了,实时轮流司机服务器端临时队列,只要有合适的新订单产生,那么就会轮回获取新订单数据,进行语音播放,如果司机对这个订单感兴趣就可以抢单,大家注意,同一个新订单会放入满足条件的所有司机的临时队列,谁先抢到就是谁的。
@Operation(summary = "司机抢单")
@GetMapping("/robNewOrder/{driverId}/{orderId}")
public Result<Boolean> robNewOrder(@PathVariable Long driverId, @PathVariable Long orderId) {
return Result.ok(orderInfoService.robNewOrder(driverId, orderId));
}
// 在OrderInfoServiceImpl之前保存订单 方法修改
//乘客下单
@Override
public Long saveOrderInfo(OrderInfoForm orderInfoForm) {
//order_info添加订单数据
OrderInfo orderInfo = new OrderInfo();
BeanUtils.copyProperties(orderInfoForm,orderInfo);
//订单号
String orderNo = UUID.randomUUID().toString().replaceAll("-","");
orderInfo.setOrderNo(orderNo);
//订单状态
orderInfo.setStatus(OrderStatus.WAITING_ACCEPT.getStatus());
orderInfoMapper.insert(orderInfo);
//记录日志
this.log(orderInfo.getId(),orderInfo.getStatus());
//向redis添加标识
//接单标识,标识不存在了说明不在等待接单状态了
// todo 司机抢单在redis中添加标识的时候是不是也需要把orderId加上啊,要不然怎么知道是哪个订单是否存在?不可能所有订单共用一个标识吧?
redisTemplate.opsForValue().set(RedisConstant.ORDER_ACCEPT_MARK,
"0", RedisConstant.ORDER_ACCEPT_MARK_EXPIRES_TIME, TimeUnit.MINUTES);
return orderInfo.getId();
}
//司机抢单
@Override
public Boolean robNewOrder(Long driverId, Long orderId) {
//判断订单是否存在,通过Redis,减少数据库压力
if(!redisTemplate.hasKey(RedisConstant.ORDER_ACCEPT_MARK)) {
//抢单失败
throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);
}
//司机抢单
//修改order_info表订单状态值2:已经接单 + 司机id + 司机接单时间
//修改条件:根据订单id
LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(OrderInfo::getId,orderId);
OrderInfo orderInfo = orderInfoMapper.selectOne(wrapper);
//设置
orderInfo.setStatus(OrderStatus.ACCEPTED.getStatus());
orderInfo.setDriverId(driverId);
orderInfo.setAcceptTime(new Date());
//调用方法修改
int rows = orderInfoMapper.updateById(orderInfo);
if(rows != 1) {
//抢单失败
throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);
}
//删除抢单标识
redisTemplate.delete(RedisConstant.ORDER_ACCEPT_MARK);
return true;
}
/**
* 司机抢单
* @param driverId
* @param orderId
* @return
*/
@GetMapping("/order/info/robNewOrder/{driverId}/{orderId}")
Result<Boolean> robNewOrder(@PathVariable("driverId") Long driverId, @PathVariable("orderId") Long orderId);
@Operation(summary = "司机抢单")
@GuiguLogin
@GetMapping("/robNewOrder/{orderId}")
public Result<Boolean> robNewOrder(@PathVariable Long orderId) {
Long driverId = AuthContextHolder.getUserId();
return Result.ok(orderService.robNewOrder(driverId, orderId));
}
@Override
public Boolean robNewOrder(Long driverId, Long orderId) {
return orderInfoFeignClient.robNewOrder(driverId,orderId).getData();
}
/**
* 司机抢单
* @param driverId
* @param orderId
* @return
*/
@GetMapping("/order/info/robNewOrder/{driverId}/{orderId}")
Result<Boolean> robNewOrder(@PathVariable("driverId") Long driverId, @PathVariable("orderId") Long orderId);
@Operation(summary = "司机抢单")
@GuiguLogin
@GetMapping("/robNewOrder/{orderId}")
public Result<Boolean> robNewOrder(@PathVariable Long orderId) {
Long driverId = AuthContextHolder.getUserId();
return Result.ok(orderService.robNewOrder(driverId, orderId));
}
@Override
public Boolean robNewOrder(Long driverId, Long orderId) {
return orderInfoFeignClient.robNewOrder(driverId,orderId).getData();
}
司机抢单优化方案
-
刚才抢单接口里面,并没有考虑并发的问题,所以,产生问题,类似于电商里面超卖问题
-
超卖问题
-
解决方案
第一种 设置数据库事务的隔离级别,设置为Serializable,效率低下
第二种 使用乐观锁解决,通过版本号进行控制
-
两个事务同时开启,第一个事务先提交之后,修改了版本,第二个事务在判断的时候版本号不一致,就没法修改数据。
-
两个事务都可以读到数据,但是最终只有一个事务可以写成功。
第三种 加锁解决,学习过synchronized 及lock锁,本地锁,目前微服务架构,分布式部署方式。
我们使用分布式锁方式解决相关问题
1、基于 Redis 做分布式锁
基于 REDIS 的 SETNX()、EXPIRE() 方法做分布式锁
(1)、setnx(lockkey, 1) 如果返回 0,则说明占位失败;如果返回 1,则说明占位成功
(2)、expire() 命令对 lockkey 设置超时时间,为的是避免死锁问题。
(3)、执行完业务代码后,可以通过 delete 命令删除 key
2、基于 REDISSON 做分布式锁
redisson 是 redis 官方的分布式锁组件。
基于乐观锁解决司机抢单
# 基础sql语句,实现抢单
update order_info set status =2 ,driver_id = ?,accept_time = ? where id=?
# 如果使用上面语句,产生问题:只要订单接单标识没有删除,
# 如果有很多线程请求过来,都会去更新sql语句,造成,最后提交的把之前提交数据覆盖
# 使用乐观锁解决,添加版本号
# 版本号
update order_info set status =2 ,driver_id = ?,accept_time = ? where id=? and status = 1
# 在where后面添加条件,status=1,相当于添加版本号
//司机抢单:乐观锁方案解决并发问题
public Boolean robNewOrder1(Long driverId, Long orderId) {
//判断订单是否存在,通过Redis,减少数据库压力
if(!redisTemplate.hasKey(RedisConstant.ORDER_ACCEPT_MARK)) {
//抢单失败
throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);
}
//司机抢单
//update order_info set status =2 ,driver_id = ?,accept_time = ?
// where id=? and status = 1
LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(OrderInfo::getId,orderId);
wrapper.eq(OrderInfo::getStatus,OrderStatus.WAITING_ACCEPT.getStatus());
//修改值
OrderInfo orderInfo = new OrderInfo();
orderInfo.setStatus(OrderStatus.ACCEPTED.getStatus());
orderInfo.setDriverId(driverId);
orderInfo.setAcceptTime(new Date());
//调用方法修改
int rows = orderInfoMapper.update(orderInfo,wrapper);
if(rows != 1) {
//抢单失败
throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);
}
//删除抢单标识
redisTemplate.delete(RedisConstant.ORDER_ACCEPT_MARK);
return true;
}
本地锁的局限性
- 之前学习过锁机制,synchronized 及lock锁,都是本地锁,只在当前jvm生效
- 举例演示
@Tag(name = "测试接口")
@RestController
@RequestMapping("/order/test")
public class TestController {
@Autowired
private TestService testService;
@GetMapping("testLock")
public Result testLock() {
testService.testLock();
return Result.ok();
}
}
@Service
public class TestServiceImpl implements TestService{
@Autowired
private StringRedisTemplate redisTemplate;
@Override
public synchronized void testLock() {
//从redis里面获取数据
String value = redisTemplate.opsForValue().get("num");
if(StringUtils.isBlank(value)) {
return;
}
//把从redis获取数据+1
int num = Integer.parseInt(value);
//数据+1之后放回到redis里面
redisTemplate.opsForValue().set("num",String.valueOf(++num));
}
}
在redis添加初始值,num=0
- 测试,模拟100请求并发过程
使用测试工具jmeter实现功能测试
- 上面测试方式,在一个服务器里面进行的测试,单机版服务。比如部署集群效果,锁还会生效?
使用idea部署集群效果
第一步 修改nacos配置中心端口配置,端口部分注释掉
第二步 项目配置文件中添加端口号 bootstrap.properties
第三步 操作idea添加多个端口号
第四步 启动集群服务,还需要启动网关服务
- 网关模块配置文件添加路由规则
- 修改测试工具端口号,是网关端口号
实现分布式锁-Redisson
redis实现分布式锁,我感觉谷粒商城的更清晰一些,谷粒商城十三缓存与分布式锁
概述
- Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。
- Redisson的宗旨是:促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。
在common里面service-util引入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
</dependency>
@Data
@Configuration
@ConfigurationProperties(prefix = "spring.data.redis")
public class RedissonConfig {
private String host;
private String password;
private String port;
private int timeout = 3000;
private static String ADDRESS_PREFIX = "redis://";
/**
* 自动装配
*
*/
@Bean
RedissonClient redissonSingle() {
Config config = new Config();
if(!StringUtils.hasText(host)){
throw new RuntimeException("host is empty");
}
SingleServerConfig serverConfig = config.useSingleServer()
.setAddress(ADDRESS_PREFIX + this.host + ":" + port)
.setTimeout(this.timeout);
if(StringUtils.hasText(this.password)) {
serverConfig.setPassword(this.password);
}
return Redisson.create(config);
}
}
@Autowired
private RedissonClient redissonClient;
//Redisson实现
@Override
public void testLock() {
//1 通过redisson创建锁对象
RLock lock = redissonClient.getLock("lock1");
//2 尝试获取锁
//(1) 阻塞一直等待直到获取到,获取锁之后,默认过期时间30s
lock.lock();
//(2) 获取到锁,锁过期时间10s
// lock.lock(10,TimeUnit.SECONDS);
//(3) 第一个参数获取锁等待时间
// 第二个参数获取到锁,锁过期时间
// try {
// // true
// boolean tryLock = lock.tryLock(30, 10, TimeUnit.SECONDS);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
//3 编写业务代码
//1.先从redis中通过key num获取值 key提前手动设置 num 初始值:0
String value = redisTemplate.opsForValue().get("num");
//2.如果值为空则非法直接返回即可
if (StringUtils.isBlank(value)) {
return;
}
//3.对num值进行自增加一
int num = Integer.parseInt(value);
redisTemplate.opsForValue().set("num", String.valueOf(++num));
//4 释放锁
lock.unlock();
}
基于Redis的Redisson分布式可重入锁RLock Java对象实现了java.util.concurrent.locks.Lock接口。
Redisson内部提供了一个监控锁的看门狗。
看门狗原理:
只要线程一加锁成功,就会启动一个watch dog
看门狗,它是一个后台线程,会每隔10
秒检查一下,如果线程一还持有锁,那么就会不断的延长锁key
的生存时间。因此,Redisson
就是使用Redisson
解决了锁过期释放,业务没执行完的问题(例如业务需要40s,锁只有30s)。
1、如果我们指定了锁的超时时间,就发送给Redis执行脚本,进行占锁,默认超时就是我们制定的时间,不会自动续期;
2、如果我们未指定锁的超时时间,就使用lockWatchdogTimeout = 30 * 1000
【看门狗默认时间】
添加Redisson分布式锁到司机抢单
//Redisson分布式锁
//司机抢单
@Override
public Boolean robNewOrder(Long driverId, Long orderId) {
//判断订单是否存在,通过Redis,减少数据库压力
if(!redisTemplate.hasKey(RedisConstant.ORDER_ACCEPT_MARK)) {
//抢单失败
throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);
}
//创建锁
RLock lock = redissonClient.getLock(RedisConstant.ROB_NEW_ORDER_LOCK + orderId);
try {
//获取锁
boolean flag = lock.tryLock(RedisConstant.ROB_NEW_ORDER_LOCK_WAIT_TIME,RedisConstant.ROB_NEW_ORDER_LOCK_LEASE_TIME, TimeUnit.SECONDS);
if(flag) {
if(!redisTemplate.hasKey(RedisConstant.ORDER_ACCEPT_MARK)) {
//抢单失败
throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);
}
//司机抢单
//修改order_info表订单状态值2:已经接单 + 司机id + 司机接单时间
//修改条件:根据订单id
LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(OrderInfo::getId,orderId);
OrderInfo orderInfo = orderInfoMapper.selectOne(wrapper);
//设置
orderInfo.setStatus(OrderStatus.ACCEPTED.getStatus());
orderInfo.setDriverId(driverId);
orderInfo.setAcceptTime(new Date());
//调用方法修改
int rows = orderInfoMapper.updateById(orderInfo);
if(rows != 1) {
//抢单失败
throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);
}
//删除抢单标识
redisTemplate.delete(RedisConstant.ORDER_ACCEPT_MARK);
}
}catch (Exception e) {
//抢单失败
throw new GuiguException(ResultCodeEnum.COB_NEW_ORDER_FAIL);
}finally {
//释放
if(lock.isLocked()) {
lock.unlock();
}
}
return true;
}
我们公司对员工是选拔制,不是培养制,学生在选择来不来华为的时候,要考虑工作适宜不适宜你的兴趣爱好。不适宜就别来,浪费了你的青春。我们不能为学生设计命运。你进华为时就要考虑适应性,不能只看到工资高,要看给你的岗位是否符合你的兴趣爱好,你愿不愿意在这里贡献。华为不能容纳天下人,也不可能让天下人适应。
https://baijiahao.baidu.com/s?id=1760664270073856317&wfr=spider&for=pc
擦亮花火、共创未来——任正非在“难题揭榜”花火奖座谈会上的讲话
任正非