在后端开发中,有一些技巧能使我们的开发更加简洁优雅,可扩展性更强,减少代码之间的耦合性。
前端按钮后端控制
在之前的开发中,前端需要根据不同的状态来显示按钮的显示隐藏,这需要前后端来协商统一,但是现实情况很多是随着业务的开发扩展以及后期测试的bug修复这些状态逻辑会相应的做出修改和删减,很有可能前后端没有进行逻辑的同步导致一些差错和不愉快,所以需要后端来避免这些不必要的情况。
//1、返回vo中声明一个操作控制类,@Getter可以不声明变量把这个变量注入到返回类里
@Getter
public AllowOperation getAllowOperationVO() {
//设置订单的可操作状态
return new AllowOperation(this);
}
//2、控制类中根据业务对不同显示按钮返回布尔值给前端,初始化这个类时可以传入不同的返回值来进行复用
@Data
public class AllowOperation implements Serializable {
private static final long serialVersionUID = -5109440403955543227L;
@ApiModelProperty(value = "可以取消")
private Boolean cancel = false;
@ApiModelProperty(value = "可以支付")
private Boolean pay = false;
@ApiModelProperty(value = "可以发货")
private Boolean ship = false;
/**
* 根据各种状态构建对象
*
* @param order
*/
public AllowOperation(Order order) {
//获取订单类型
String status = order.getOrderStatus();
String payStatus = order.getPayStatus();
//编辑订单价格 未付款并且是新订单
if (payStatus.equals(PayStatusEnum.UNPAID.name()) && status.equals(OrderStatusEnum.UNPAID.name())) {
this.editPrice = true;
}
//新订单,允许支付
this.pay = status.equals(OrderStatusEnum.UNPAID.name()) && payStatus.equals(PayStatusEnum.UNPAID.name());
........
}
}
查询列表接口入参统一封装
开发中涉及大量的列表查询,需要做分页、条件查询等,这些操作具有高度的相似性,所以需要进行封装省时省力
//1、列表入参dto继承分页父类,实现条件查询
public class OrderSearchParams extends PageVO {
@ApiModelProperty(value = "关键字")
private String keywords;
public <T> QueryWrapper<T> queryWrapper() {
QueryWrapper<T> wrapper = new QueryWrapper<>();
//关键字查询
if (StrUtil.isNotEmpty(keywords)) {
wrapper.like("o.sn", keywords);
wrapper.like("oi.goods_name", keywords);
}
//按卖家查询
wrapper.eq(StrUtil.equals(UserContext.getCurrentUser().getRole().name(), UserEnums.STORE.name()), "o.store_id", UserContext.getCurrentUser().getStoreId());
}
}
//2、分页父类实现分页功能
@Data
public class PageVO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "页号")
private Integer pageNumber = 1;
@ApiModelProperty(value = "页面大小")
private Integer pageSize = 10;
@ApiModelProperty(value = "排序字段")
private String sort;
@ApiModelProperty(value = "排序方式 asc/desc")
private String order;
@ApiModelProperty(value = "需要驼峰转换蛇形", notes = "一般不做处理,如果数据库中就是蛇形,则这块需要处理。")
private Boolean notConvert;
public Integer getMongoPageNumber() {
int i = pageNumber - 1;
if (i < 0) {
return pageNumber;
} else {
return i;
}
}
public String getSort() {
if (!StringUtils.isEmpty(sort)) {
if (notConvert == null || Boolean.FALSE.equals(notConvert)) {
return StringUtils.camel2Underline(sort);
} else {
return sort;
}
}
return sort;
}
}
//3、业务代码查询
public IPage<OrderSimpleVO> queryByParams(OrderSearchParams orderSearchParams) {
QueryWrapper queryWrapper = orderSearchParams.queryWrapper();
return this.baseMapper.queryByParams(PageUtil.initPage(orderSearchParams), queryWrapper);
}
系统、业务日志存储
对不同业务进行日志的记录是开发中很常见的事情,包括系统日志、异常日志等,利用spring的aop切面特性我们可以很好的处理以及后期进行日常维护
//1、接口申明需要传入的变量
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AfterSaleLogPoint {
/**
* 日志名称
*
* @return
*/
String description();
/**
* 售后SN
*
* @return
*/
String sn();
}
//2、具体的实现类
@Slf4j
@Aspect
@Component
public class AfterSaleOperationLogAspect {
@Autowired
private AfterSaleLogService afterSaleLogService;
@AfterReturning(returning = "rvt", pointcut = "@annotation(com.haifansafe.common.aspect.annotation.AfterSaleLogPoint)") //AfterReturning:切点的执行顺序时间,pointcut:切点的位置
public void afterReturning(JoinPoint joinPoint, Object rvt) {
try {
AuthUser auth = UserContext.getCurrentUser();
//日志对象拼接
//默认操作人员,系统操作
String userName = "系统操作", id = "-1", role = UserEnums.SYSTEM.getRole();
if (auth != null) {
//日志对象拼接
userName = UserContext.getCurrentUser().getUsername();
id = UserContext.getCurrentUser().getId();
role = UserContext.getCurrentUser().getRole().getRole();
}
Map<String, String> afterSaleLogPoints = spelFormat(joinPoint, rvt);
AfterSaleLog afterSaleLog = new AfterSaleLog(afterSaleLogPoints.get("sn"), id, role, userName, afterSaleLogPoints.get("description"));
//调用线程保存
ThreadPoolUtil.getPool().execute(new SaveAfterSaleLogThread(afterSaleLog, afterSaleLogService));
} catch (Exception e) {
log.error("售后日志错误",e);
}
}
}
//3、在方法处声明
@AfterSaleLogPoint(sn = "#orderItemSn", description = "'售后申请:售后编号['+#orderItemSn+']'")
public void saveAfterSale(String orderItemSn) {
.......
}
业务代码的抽取和解耦
当某项业务实现很复杂,需要大量逻辑代码时,仅仅靠注释很难阐明流程和逻辑。另外在一个类里进行大量代码的堆砌会使该类不断的臃肿,后期对该类的维护会很麻烦(之前一个类五千多行代码,我光编辑就会很卡,上传git也很慢,体验极差),所以需要进行代码解耦。
//1、用集合类对业务进行详细拆分
@Autowired
private List<CartRenderStep> cartRenderSteps;
//按照计划进行渲染
for (int index : cartRender) {
try {
cartRenderSteps.get(index).render(tradeDTO);
} catch (Exception e) {
log.error("购物车{}渲染异常:", cartRenderSteps.get(index).getClass(), e);
}
}
//2、具体业务实现类,还用@Order可以指定执行顺序
@Order(5)
@Service
public class CartPriceRender implements CartRenderStep {
.........
}