学工系统-低值易耗品管理
时间: 2023.6.21-2023.7.9
实现功能
实现对学校低值易耗品管理,包括功能:
- 易耗品出入库
- 单个/批量导入导出
- 易耗品出入库申请
- 提交申请 -> 审批列表 -> 审批详情 -> 审批(通过/驳回)
- 生成审批表(PDF) -> 导出审批记录
- 撤销申请
- 易耗品领用/归还
- 提交申请 -> 审批列表 -> 审批详情 -> 审批(通过/驳回)
- 生成审批表(PDF) -> 导出审批记录
- 撤销申请
个人提升
- 首次接触文件操作
- 批量导入、批量导出(Excel表)
- 生成审批表(生成Word -> 转为PDF)
- 导出审批记录(生成Excel)
学工系统-动态表单
时间:
- 2023.7.11-2023.8.3
- 2023.8.21-2023.10.27
实现功能
任何 提交申请 -> 审批列表 -> 审批详情 -> 审批(通过/驳回) 流程的表单均可快速生成,只需下面几步:
- 设置应用基本信息
- 选择控件
- 设置流程
- 可以动态设置多条流程,满足什么条件(根据控件设置条件)遵循某条流程
- 控件可设置权限,某个流程可读或可编辑某个控件信息
- 上传审批生成的PDF模板
- 发布应用
数据库设计
自上而下依次为:
- 应用申请表: 存放申请表数据
- 应用用户申请数据表: 存放用户申请表数据
- 应用基础信息表
- 流程条件表
- 流程条件组表
- 条件展示表
- 控件顺序表
- 应用文件控件配置表
- 应用选项控件配置表
- 流程节点配置表
- 流程节点数据表
- 节点回退记录表
- 应用选项控件配置表
- 应用模板表: 存放各个应用模板文件名
- 应用文本控件配置表
- 应用时间控件配置表
接口
应用基础信息
- 添加应用基础设置
- 删除应用
- 修改基础设置
- 查询基础设置
- 模板上传
应用控件配置
- 保存表单设计
- 修改/编辑表单设计
- 展示应用控件及配置
审批流程配置 [难点]
- 展示流程节点
- 条件展示
- 添加条件展示
- 展示条件节点
- 展示可设置条件控件
- 保存条件节点
- 添加流程节点
- 添加条件分支
- 添加条件
- 删除流程节点
- 删除条件节点
- 修改流程节点
应用使用
- 提交申请表数据并生成流程
- 提交数据和流程生成申请表
- 撤销申请
- 审批列表
- 审批详情
- 审批(通过/拒绝)
- 转交
- 回退
- 操作记录部分
- 展示所有申请表信息
- 获取某个应用控件字段信息
- 批量导出
- 强制归档(所有流程默认通过)
- 强制流转(流转审批人)
- 模板下载(下载单个申请表PDF)
- 代审批
- 强制驳回
- 删除单个申请表数据
用到的算法
栈
添加条件和删除条件节点接口使用到了栈来寻找 虚拟节点(聚合节点)
java
// 记录当前节点(发散节点)
AppProcessNodeConfig dummyNodeUp = dummyNode;
// 获取当前 虚拟节点(发散节点) 所对应的 虚拟节点(聚合节点)
Stack<AppProcessNodeConfig> dummyNodeStack = new Stack<>();
dummyNodeStack.push(dummyNode);
while (!dummyNodeStack.empty()) {
dummyNode = getNextNode(dummyNode); // 获取虚拟节点的下一个节点
List<String> prevIdList = JSONObject.parseArray(dummyNode.getPrevId(), String.class);
List<String> nextIdList = JSONObject.parseArray(dummyNode.getNextId(), String.class);
if (nextIdList.size() > 1) { // 子节点有多个
dummyNodeStack.push(dummyNode);
} else if (prevIdList.size() > 1) { // 父节点有多个
if (dummyNodeStack.size() == 1) {
break;
}
dummyNodeStack.pop();
}
}
// 此时 dummyNode 为 虚拟节点(发散节点) 所对应的 虚拟节点(聚合节点)
递归
java
/**
* 获取传入的 两个节点之间 所有节点的id集合 [左闭右开]
*
* @param node 当前节点
* @param targetNode 目标节点
* @param nodeIdList 用来存放id的集合
*/
public void getIdList(AppProcessNodeConfig node, AppProcessNodeConfig targetNode, HashSet<String> nodeIdList) {
if (!node.getId().equals(targetNode.getId())) { // 当前节点不等于目标节点
// 把当前 节点id 存入 nodeIdList
nodeIdList.add(node.getId());
// 递归向下查找
for (AppProcessNodeConfig nextNode : getNextNodeList(node)) {
getIdList(nextNode, targetNode, nodeIdList);
}
}
}
个人提升
- 逻辑思维能力得到了很大的提升,复杂接口也能手到拈来
示例:
java
@ApiModelProperty(value = "提交申请表数据并生成流程")
@PostMapping("/submitData")
public ResponseResult submitApplicationData(@RequestBody SubmitApplicationDataReq req, HttpServletRequest request) {
String userId = jwtTokenUtil.getUserIdByRequest(request);
return appApplicationDataService.submitApplicationData(req, userId);
}
实现:
java
@Override
public ResponseResult submitApplicationData(SubmitApplicationDataReq req, String userId) {
//校验参数
if (isNullOrEmpty(req.getAppId(), req.getDataReqList())) {
return CommonResult.failed(CommonCodeEnum.INVALID_PARAM);
}
//校验 数据数量 与 控件数量 是否匹配
List<String> controlOrderIdList = req.getDataReqList().stream().map(ApplicationDataReq::getControlOrderId).collect(Collectors.toList());
Integer controlNum = appControlOrderMapper.selectCount(new LambdaQueryWrapper<AppControlOrder>()
.eq(AppControlOrder::getAppId, req.getAppId())
.in(AppControlOrder::getId, controlOrderIdList));
if (controlNum != req.getDataReqList().size()) {
return CommonResult.failed(CommonCodeEnum.DATA_NUM_NOT_EQUALS_CONTROL_NUM);
}
//校验必填控件是否全部填写
for (ApplicationDataReq dataReq : req.getDataReqList()) {
if (isNullOrEmpty(dataReq.getControlOrderId())) {
return CommonResult.failed(CommonCodeEnum.INVALID_PARAM);
}
Boolean isRequired = appDynamicUtil.getIsRequired(dataReq.getControlOrderId());
if (isRequired == null) {
return CommonResult.failed(CommonCodeEnum.CONTROL_NOT_EXIST);
}
if (isRequired) {
if (isNullOrEmpty(dataReq.getData())) {
return CommonResult.failed(CommonCodeEnum.REQUIRED_CONTROL_NOT_FILL);
}
}
}
List<AppProcessNodeConfigResp> processNodeRespList = new ArrayList<>();
//获取申请人节点配置
AppProcessNodeConfig nodeConfig = appProcessNodeConfigMapper.selectList(new LambdaQueryWrapper<AppProcessNodeConfig>()
.eq(AppProcessNodeConfig::getAppId, req.getAppId())
.eq(AppProcessNodeConfig::getType, APPLICANT_NODE)).get(0);
//记录节点顺序
int sort = 0;
while (nodeConfig.getType() != END_NODE) {
//添加当前节点
AppProcessNodeConfigResp nodeConfigResp = new AppProcessNodeConfigResp();
BeanUtils.copyProperties(nodeConfig, nodeConfigResp);
//设置节点顺序
nodeConfigResp.setSort(sort++);
//设置审批人信息
if (nodeConfigResp.getApproverType() == DYNAMIC_APP_ROLE) {
SmsRole role = smsRoleMapper.selectById(nodeConfigResp.getApproverObjectId());
nodeConfigResp.setObjectName(role.getRoleName());
} else if (nodeConfigResp.getApproverType() == DYNAMIC_APP_ASSIGNER) {
SmsUser user = smsUserMapper.selectById(nodeConfigResp.getApproverObjectId());
AppSearchUserResp userResp = new AppSearchUserResp();
BeanUtils.copyProperties(user, userResp);
nodeConfigResp.setUserInfo(userResp);
} else if (nodeConfigResp.getApproverType() == DYNAMIC_APP_SUBMITTER) {
SmsUser user = smsUserMapper.selectById(userId);
AppSearchUserResp userResp = new AppSearchUserResp();
BeanUtils.copyProperties(user, userResp);
nodeConfigResp.setUserInfo(userResp);
nodeConfigResp.setApproverType(DYNAMIC_APP_ASSIGNER);
nodeConfigResp.setApproverObjectId(userId);
}
//设置权限
nodeConfigResp.setAuthority(JSONObject.parseArray(nodeConfig.getAuthority(), AuthorityResp.class));
//添加本节点
processNodeRespList.add(nodeConfigResp);
//节点向后传递
List<AppProcessNodeConfig> nextNodeList = appDynamicUtil.getNextNodeList(nodeConfig);
a:
for (AppProcessNodeConfig nextNode : nextNodeList) {
while (true) {
if (nextNode.getType() == COPY_NODE || nextNode.getType() == APPROVE_NODE || nextNode.getType() == END_NODE) {
nodeConfig = nextNode;
break a;
} else if (nextNode.getType() == DUMMY_NODE) {
List<String> nextNodeSonIdList = JSON.parseArray(nextNode.getNextId(), String.class);
//虚拟节点(发散节点)
if (nextNodeSonIdList.size() > 1) {
for (int priority = 1; priority <= nextNodeSonIdList.size(); priority++) {
//根据 前节点id 及 优先级 检索到条件组集合
List<AppConditionGroup> conditionGroupList = appConditionGroupMapper.selectList(new LambdaQueryWrapper<AppConditionGroup>()
.eq(AppConditionGroup::getPrevNodeId, nextNode.getId())
.eq(AppConditionGroup::getPriority, priority));
//校验条件组是否存在
if (conditionGroupList.isEmpty()) {
return CommonResult.failed(CommonCodeEnum.APP_CONDITION_GROUP_NOT_EXIST);
}
//若当前条件组为默认条件
if (priority == nextNodeSonIdList.size() && conditionGroupList.get(0).getIsDefault()) {
AppProcessNodeConfig node = appProcessNodeConfigMapper.selectById(conditionGroupList.get(0).getNodeId());
nodeConfig = appDynamicUtil.getNextNode(node);
break a;
}
//遍历条件组中的条件是否完成
for (AppConditionGroup conditionGroup : conditionGroupList) {
List<AppCondition> conditionList = appConditionMapper.selectList(new LambdaQueryWrapper<AppCondition>()
.eq(AppCondition::getConditionGroupId, conditionGroup.getId()));
//校验条件是否存在
if (conditionList.isEmpty()) {
return CommonResult.failed(CommonCodeEnum.APP_CONDITION_NOT_EXIST);
}
//校验条件集合是否全部符合
boolean isReachedConditionList = appDynamicUtil.getIsReachedConditionList(req.getDataReqList(), conditionList, userId);
//若 条件组中 有 条件集合全部符合
if (isReachedConditionList) {
AppProcessNodeConfig node = appProcessNodeConfigMapper.selectById(conditionGroupList.get(0).getNodeId());
nodeConfig = appDynamicUtil.getNextNode(node);
break a;
}
}
}
} else {//虚拟节点(聚合节点)
nextNode = appDynamicUtil.getNextNodeList(nextNode).get(0);
}
}
}
}
}
return CommonResult.success(processNodeRespList, processNodeRespList.size());
}
学工系统-会议室管理
时间: 2023.8.3-2023.8.29
实现功能
学校对于会议室进行预约、申请、管理等操作
接口
- 楼栋管理
- 添加楼栋
- 删除楼栋
- 展示楼栋树
- 会议室管理
- 添加会议室
- 删除会议室
- 编辑会议室
- 展示会议室
- 展示规定时间段可用会议室
- 会议室预约
- 提交预定申请
- 撤销预定申请
- 审批列表
- 审批详情
- 审批(同意/拒绝)
- 展示指定日期会议室列表及时间
- 展示指定会议室信息及时间细节(按周展示)
个人提升
- 对关于时间的操作更上一层楼
- 对某周内每一天数据进行查询数据库并包装
- 校验当前时间段是否存在会议
- 校验开始时间和结束时间限制
- 校验是否超出会议室单次可预订时长上限
- 校验是否超出会议室最早可提前预定时间
- ...
南充经济开发区化工管廊集控平台
时间: 2023.10.25-2023.11.19
实现功能
- 与第三方厂商对接,完成对视频监控(浙江大华技术股份有限公司)、气体监测数据整合并展示到大屏中。
- 后台需实现对设备(视频监控、气体监测器)的管理
- 数据统计
接口
- 视频监控
- 获取鉴权
- 刷新认证信息
- 获取组织树
- 获取监控设备列表
- 监控实时预览
- 监控录像回放
- ...
- 气体监测
- 更新当前登录⼈最新的全部分组下所有设备所有探头
- 获取设备在线离线列表数据
- 获取当前登录⼈全部的分组
- 获取大屏展示探测器的数据
- 获取设备下探测器列表
- 获取探测器历史数据+故障和告警列表
- 获取首页的信息分析
- 获取告警分析
- 获取设备报警排名
- ...
个人提升
-
对接第三方厂商经验得到提升
-
对接视频监控
-
添加第三方依赖
xml<!-- ICC鉴权 --> <dependency> <groupId>com.dahuatech.icc</groupId> <artifactId>java-sdk-oauth</artifactId> <version>${icc.sdk.version}</version> <exclusions> <exclusion> <artifactId>java-sdk-core</artifactId> <groupId>com.dahuatech.icc</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>com.dahuatech.icc</groupId> <artifactId>java-sdk-core</artifactId> <version>${icc.sdk.version}</version> </dependency> <!-- ICC基础资源SDK --> <dependency> <groupId>com.dahuatech.icc</groupId> <artifactId>java-sdk-oauth</artifactId> <version>${icc.sdk.version}</version> </dependency> <!-- ICC 事件中心sdk --> <dependency> <groupId>com.dahuatech.icc</groupId> <artifactId>java-sdk-event</artifactId> <version>${icc.sdk.version}</version> </dependency>
-
根据第三方系统规则获取鉴权
-
带鉴权、签名等(根据第三方规则)去请求第三方接口获取到数据,对数据进行处理(存数据库/返回给前端展示)
-
-
对接气体监测
-
添加
mqtt
依赖xml<dependency> <groupId>com.github.tocrhz</groupId> <artifactId>mqtt-spring-boot-starter</artifactId> <version>1.2.7</version> </dependency>
-
利用第三方规则请求其接口再对回应数据进行处理
java/** * 获取token * @return token */ public String getGasToken() { String gasValue = cacheUtil.getObject(gasKey, String.class); // redis中获取到token if (!isNullOrEmpty(gasValue)) { // log.info("redis中有access_token:{}", gasValue); return gasValue; } // redis未获取token,请求token String gasTokenResult = OkHttpUtils.builder().url(tokenUrl) .addParam("appKey", gasConfigProperties.getApp_key()) .addParam("appSecret", gasConfigProperties.getApp_secret()) .post(true) .async(); JSONObject gasTokenJsonObject = JSONObject.parseObject(gasTokenResult); // {"code":0,"data":{"access_token":"da8c3b33-972d-4f8c-a839-e0e913ad465c"}} // 校验code Integer code = gasTokenJsonObject.getInteger("code"); if (code != 0) { ExceptionCast.cast(CommonResult.failed(CommonCodeEnum.GAS_ACCESS_TOKEN_FAIL)); } // 获取access_token String access_token = gasTokenJsonObject.getJSONObject("data").getString("access_token"); cacheUtil.add(gasKey, access_token, 2, TimeUnit.HOURS); // log.info("access_token:{}", access_token); return access_token; }
-
利用
mqtt
监听器监听到的数据有选择性地对数据库进行操作(插入、修改、删除等)java/** * 接受来⾃设备的实时浓度数据 * @param topic 主题 * @param groupId 分组id * @param deviceId 设备id * @param slaveEventResp 每个探测器 15分钟数据上传⼀次(设备上报属性) */ @MqttSubscribe(value = lastTopic, qos = 0) @Transactional public void subLast(String topic,@NamedValue("groupId") String groupId,@NamedValue("deviceId") String deviceId,@Payload String slaveEventResp) { // log.info("开始接受每个探测器15分钟数据上传⼀次......"); // log.info("receive from : {}", topic); // log.info("groupId :{}",groupId); // log.info("deviceId : {}",deviceId); // log.info("设备上报 : {}", slaveEventResp); updateSlaveData(groupId,deviceId,SLAVE_REPORT_TYPE,slaveEventResp); // log.info("接受每个探测器15分钟数据上传⼀次结束!!!!!!"); }
-
家校通项目
时间: 2023.11.8-2023.12.12
实现功能
- 对接第三方厂商门禁系统
- 接收门禁闸机发送的请求并对数据进行处理
- 实现对学生出入校进行记录(时间、照片等)
- 家长可绑定学生信息(一个家长可绑定多个学生)
- 家长可以增删改查学生基本信息
- 家长可以查看学生的出入校记录以及门禁照片
- 家长可以修改学生门禁照片
接口
-
平台主体
- 获取绑定学生信息
- 获取人脸识别记录
- 获取所有学生出入校记录
- 学生家长信息批量绑定
- 展示学生家长绑定信息
- 批量导入家长信息
- 展示家长信息
- 查看当前门禁系统无照片人员信息
- 批量导出绑定信息
- 批量导出出入校记录
-
接收闸机请求
- 接收人脸识别记录
-
与第三方平台交互
-
城市社区
-
楼栋管理
- 创建楼栋
- 更新楼栋
- 删除楼栋
- 查询社区所有楼栋
-
房屋管理
- 创建房屋
- 更新房屋
- 删除房屋
- 查询楼栋内所有房屋
-
住户管理
- 添加住户信息
- 更新住户信息
- 删除住户信息
- 绑定住户信息
- 解绑住户信息
- 白名单下发
- 白名单删除
- 住户下发
- 查询房屋内住户
-
设备管理
- 设备通行记录
- 设置设备参数
- ...
-
-
门禁信息
- 更新个人门禁信息
- 获取个人门禁信息
- 查看当前门禁系统照片
-
其他
- 学生走住读批量导入
- 查询学生列表
- 修改学生状态
- 查看走住读列表
- ...
个人提升
- 对文件的操作更加熟练文件操作
- 对第三方厂商平台对接更加得心应手
医药销售APP及管理后台
时间: 2023.11.29-2024.1.4 [第一阶段完成]
负责模块
- 药品活动管理
- 优惠券[后台管理与客户端领取及使用]
- 收货地址管理
- 购物车
- 积分商城
- 订单模块
- 金额计算
- 对接微信支付
- 下单
- 退款
- 定时任务
- 后续需对接
- 快递及发票第三方平台
- 对接支付宝支付、云闪付支付等
接口
这里我主要展示自己第一次接触的订单模块,其他模块都基本上是增删改查操作以及对文件的处理
微信支付回调模块涉及公司业务不变展示,大家有任何问题可以私聊我了解.
有不对的或者可以改进的地方都可以一起交流,有则改之无则加勉!
- 订单模块
- 计算金额
- 结算
- 提交订单
- 取消订单
- 我的订单
- 订单详情
- 确认发货
- 确认收货
- 获取所有订单
- 微信支付回调
- 退款模块
- 申请退款
- 同意退款
- 拒绝退款
- 获取全部退款信息
- 获取退款详情信息
定时任务
ScheduleConfig
java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@Configuration
@EnableScheduling
public class ScheduleConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
}
@Bean
public Executor taskExecutor() {
return Executors.newScheduledThreadPool(10); //指定线程池大小
}
}
OrderScheduleTask
java
@Component
@Log4j2
public class OrderScheduleTask {
@Resource
private MmsOrderScheduleService mmsOrderScheduleService;
@Resource
private MmsOrderService mmsOrderService;
@Resource
private MmsOrderRefundService mmsOrderRefundService;
@Resource
private WxPayUtil wxPayUtil;
@Resource
private OrderUtil orderUtil;
@Resource
private MmsUserCouponRelationService mmsUserCouponRelationService;
@Resource
private PlatformTransactionManager transactionManager; // 事务
@Scheduled(cron = "0 */1 * * * ?") // 每隔1分钟执行一次
public void schedulingTask() {
// 获取所有 未结束且已达过期时间 的定时任务
Date nowDate = new Date(); // 当前时间
List<MmsOrderSchedule> orderScheduleList = mmsOrderScheduleService.list(new LambdaQueryWrapper<MmsOrderSchedule>()
.eq(MmsOrderSchedule::getIsClose, false) // 状态未结束
.le(MmsOrderSchedule::getExpireTime, nowDate)); // 过期时间 早于 当前时间
if (orderScheduleList.isEmpty()) { // 没有定时任务直接结束方法
return;
}
for (MmsOrderSchedule orderSchedule : orderScheduleList) {
// 开启事务
DefaultTransactionDefinition dt = new DefaultTransactionDefinition();
// 嵌套事务 PROPAGATION_REQUIRES_NEW 每次开启一个新的事务
dt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
// 设置嵌套事务
TransactionStatus status = transactionManager.getTransaction(dt);
try {
// 获取定时任务订单
MmsOrder order = mmsOrderService.getById(orderSchedule.getOrderId());
if (order == null) { //订单不存在
continue;
}
// 根据 进度类型(0.支付进度 1.发货进度 2.订单结束进度) 进行不同处理
switch (orderSchedule.getType()) {
case OrderScheduleConstant.ORDER_SCHEDULE_TYPE_PAYMENT: // 支付进度
// 主动查询用户支付情况
Transaction transaction = wxPayUtil.queryOrderByOutTradeNo(order.getId());
// 用户支付成功
if (transaction != null && transaction.getTradeState() == Transaction.TradeStateEnum.SUCCESS) {
if (!orderUtil.isPaid(order.getId())) {
// 修改订单状态等信息
order.setPaymentTransactionId(transaction.getTransactionId()); // 支付订单号
order.setPaymentAmount(centsToYuan(transaction.getAmount().getTotal())); // 支付金额
order.setPaymentMethod(OrderConstant.ORDER_PAYMENT_METHOD_WECHAT); // 订单支付方式: 微信支付
order.setPaymentTime(TimeUtil.rfc3339ParseDate(transaction.getSuccessTime())); // 支付时间
order.setOrderStatus(OrderConstant.ORDER_STATUS_TO_BE_DELIVERED); // 待发货
}
} else { // 支付失败
order.setOrderStatus(OrderConstant.ORDER_STATUS_ORDER_CANCELLED); // 订单状态: 0.取消订单
order.setIsClose(OrderConstant.ORDER_IS_CLOSE_YES); // 结束订单
order.setEndTime(nowDate); // 订单结束时间
}
break;
case OrderScheduleConstant.ORDER_SCHEDULE_TYPE_DELIVERY: // 发货进度
order.setOrderStatus(OrderConstant.ORDER_STATUS_ORDER_CANCELLED); // 订单状态: 0.取消订单
order.setIsClose(OrderConstant.ORDER_IS_CLOSE_YES); // 结束订单
order.setEndTime(nowDate); // 订单结束时间
// 付款金额大于0,自动退款
if (order.getPaymentAmount().compareTo(BigDecimal.valueOf(0)) > 0) {
// 查询不到支付订单号直接回滚
if (isNullOrEmpty(order.getPaymentTransactionId())) {
rollback(status); // 手动回滚事务
continue;
}
// 待退款中的订单改为退款失败
List<MmsOrderRefund> orderRefundList = mmsOrderRefundService.list(new LambdaQueryWrapper<MmsOrderRefund>()
.eq(MmsOrderRefund::getOrderId, order.getId()) // 订单编号
.eq(MmsOrderRefund::getStatus, OrderRefundConstant.ORDER_REFUND_STATUS_TO_BE_REFUNDED)); // 待退款
if (!orderRefundList.isEmpty()) {
MmsOrderRefund orderRefund = new MmsOrderRefund();
orderRefund.setStatus(OrderRefundConstant.ORDER_REFUND_STATUS_REFUNDED_FAIL); // 退款失败
List<String> orderRefundIdList = orderRefundList.stream().map(MmsOrderRefund::getId).collect(Collectors.toList());
boolean refundUpdSuccess = mmsOrderRefundService.update(orderRefund, new LambdaQueryWrapper<MmsOrderRefund>()
.in(MmsOrderRefund::getId, orderRefundIdList));
if (!refundUpdSuccess) { //修改失败
rollback(status); // 手动回滚事务
continue;
}
}
// 记录退款行为
MmsOrderRefund orderRefund = new MmsOrderRefund();
orderRefund.setUserId(order.getUserId());
orderRefund.setOrderId(order.getId());
orderRefund.setMedicineInfo(order.getMedicineInfo());
orderRefund.setStatus(OrderRefundConstant.ORDER_REFUND_STATUS_REFUNDING); // 退货状态: 退款中
orderRefund.setProcessApproverId(ProcessApproverConstant.PROCESS_APPROVER_SYSTEM); // 审批人: 系统-定时任务
orderRefund.setNotes(OrderRefundConstant.ORDER_REFUND_REASON_DELIVERY_TIMEOUT);
boolean refundSaveSuccess = mmsOrderRefundService.save(orderRefund);
if (!refundSaveSuccess) {
rollback(status); // 手动回滚事务
continue;
}
// 发起预退款
RefundReq preRefundReq = new RefundReq();
preRefundReq.setTransaction_id(order.getPaymentTransactionId()); // 微信交易订单号
preRefundReq.setOut_trade_no(order.getId()); // 商户订单号
preRefundReq.setOut_refund_no(orderRefund.getId()); // 退款单号
preRefundReq.setReason(OrderRefundConstant.ORDER_REFUND_REASON_USER_SUBMIT); // 退款原因: 用户申请退款
// 设置订单金额 单位:分(订单金额 = 原金额(元) * 100)
long total = order.getPaymentAmount().multiply(BigDecimal.valueOf(100)).longValue();
preRefundReq.setTotal(total); // 订单金额 单位:分
preRefundReq.setRefund(total); // 退款金额 单位:分(全部退)
Refund refund = wxPayUtil.createRefund(preRefundReq);
if (refund == null) {
rollback(status); // 手动回滚事务
continue;
}
// 修改退款记录
orderRefund.setRefundId(refund.getRefundId()); // 微信退款id
orderRefund.setRefundAmount(centsToYuan(refund.getAmount().getTotal())); // 退款金额
orderRefund.setRefundTime(TimeUtil.rfc3339ParseDate(refund.getSuccessTime())); // 退款时间
orderRefund.setStatus(OrderRefundConstant.ORDER_REFUND_STATUS_REFUNDED); // 退货状态: 已退款
boolean refundUpdSuccess = mmsOrderRefundService.updateById(orderRefund);
if (!refundUpdSuccess) {
rollback(status); // 手动回滚事务
continue;
}
}
// 退还优惠券
if (!isNullOrEmpty(order.getCouponInfo())) { // 存在优惠券信息
MmsUserCouponRelationResp couponInfo = JSONObject.parseObject(order.getCouponInfo(), MmsUserCouponRelationResp.class);
if (couponInfo != null) {
MmsUserCouponRelation userCoupon = mmsUserCouponRelationService.getById(couponInfo.getId());
if (userCoupon != null) {
// 优惠券状态修改为未使用
userCoupon.setIsUse(UserCouponRelationConstant.USER_COUPON_RELATION_NOT_USE);
boolean couponUpdSuccess = mmsUserCouponRelationService.updateById(userCoupon);
if (!couponUpdSuccess) { // 修改失败
rollback(status); // 手动回滚事务
continue;
}
}
}
}
break;
case OrderScheduleConstant.ORDER_SCHEDULE_TYPE_ORDER_END: // 订单结束进度
order.setOrderStatus(OrderConstant.ORDER_STATUS_ALREADY_RECEIVED); // 订单状态: 4.已收货
order.setIsClose(OrderConstant.ORDER_IS_CLOSE_YES); // 结束订单
order.setEndTime(nowDate); // 订单结束时间
// 发起分账
// 支付订单号不存在
if (isNullOrEmpty(order.getPaymentTransactionId())) {
continue;
}
wxPayUtil.profitSharingUnfreezeOrder(order.getId(), order.getPaymentTransactionId());
// 待退款中的订单改为退款失败
List<MmsOrderRefund> orderRefundList = mmsOrderRefundService.list(new LambdaQueryWrapper<MmsOrderRefund>()
.eq(MmsOrderRefund::getOrderId, order.getId()) // 订单编号
.eq(MmsOrderRefund::getStatus, OrderRefundConstant.ORDER_REFUND_STATUS_TO_BE_REFUNDED)); // 待退款
if (!orderRefundList.isEmpty()) {
MmsOrderRefund orderRefund = new MmsOrderRefund();
orderRefund.setStatus(OrderRefundConstant.ORDER_REFUND_STATUS_REFUNDED_FAIL); // 退款失败
List<String> orderRefundIdList = orderRefundList.stream().map(MmsOrderRefund::getId).collect(Collectors.toList());
boolean refundUpdSuccess = mmsOrderRefundService.update(orderRefund, new LambdaQueryWrapper<MmsOrderRefund>()
.in(MmsOrderRefund::getId, orderRefundIdList));
if (!refundUpdSuccess) { //修改失败
rollback(status); // 手动回滚事务
continue;
}
}
break;
}
// 修改订单
boolean orderUpdSuccess = mmsOrderService.updateById(order);
if (!orderUpdSuccess) {
rollback(status); // 手动回滚事务
continue;
}
// 关闭定时任务
orderSchedule.setIsClose(true); // 结束定时任务
orderSchedule.setEndTime(nowDate); // 结束时间
orderSchedule.setUpdateUserId(ProcessApproverConstant.PROCESS_APPROVER_SYSTEM); // 修改人
boolean scheduleUpdSuccess = mmsOrderScheduleService.updateById(orderSchedule);
if (!scheduleUpdSuccess) {
rollback(status); // 手动回滚事务
continue;
}
} catch (Exception e) {
e.printStackTrace();
// 手动回滚事务
transactionManager.rollback(status);
} finally {
if (status.isNewTransaction() && !status.isCompleted()) {
transactionManager.commit(status);
}
}
}
}
/**
* 将以分为单位的金额转换为以元为单位的金额(BigDecimal)
*
* @param <T> 可以是Integer或Long类型
* @param moneyInCents 以分为单位的金额(T)
* @return 以元为单位的金额(BigDecimal)
*/
private <T extends Number> BigDecimal centsToYuan(T moneyInCents) {
// 将分转换为元, 需要除以100, 并将结果存储为BigDecimal类型
return new BigDecimal(moneyInCents.longValue()).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
}
/**
* 手动回滚事务
*
* @param status 嵌套事务
*/
private void rollback(TransactionStatus status) {
// 手动回滚事务
transactionManager.rollback(status);
if (status.isNewTransaction() && !status.isCompleted()) {
transactionManager.commit(status);
}
}
}
个人提升
-
定时任务
定时任务其实并没有多么复杂,有些需求难免会遇到需要定时任务去解决的地方,了解定时任务之后再去处理这样的需求便会简单很多了
-
微信支付
首次接触对接微信支付受益匪浅,虽然很复杂,需要很多步骤,但是实现下来成就感满满,经过对微信支付的对接,我相信对其他支付(支付宝、云闪付等)的对接会更加得心应手
总结
今年的收货特别多,技术层面以及逻辑思维能力得到了极大的提升,也交到了特别好的同事们,绝对称得上自己工作以来的一个良好开端,希望自己可以不忘初心,继续努力下去!!!