首页 > 其他分享 >全网最全-适用于OA、CRM、WMS等单体系统的项目亮点(二)

全网最全-适用于OA、CRM、WMS等单体系统的项目亮点(二)

时间:2024-07-14 14:58:23浏览次数:16  
标签:Exception return WMS OA token param key public CRM

文章目录

前言

  • 在传统的OA办公自动化、CRM客户关系管理、WMS仓储管理系统中,使用的技术栈比较老,在日常开发中,很多时间都在做业务开发,很难发掘技术亮点,并写到简历上。在撰写简历时,即使面对传统且技术栈较老的OA、CRM、WMS系统,依然可以提炼出技术亮点,本文将总结一些可以直接套用在项目中的技术亮点,并写到简历中;

项目亮点一

  • 基于Redis、自定义注解、Token机制实现接口幂等,解决XXX模块接口的数据重复提交问题

场景描述

  • 在OA系统中,一个常见的复杂场景是处理员工的请假申请。由于网络延迟、用户误操作或前端重复提交等原因,员工可能不小心提交了重复的请假申请。这不仅会给HR审批带来困扰,还可能影响考勤统计的准确性。为了解决这个问题,我们可以使用Redis结合自定义注解来实现接口幂等性,确保同一个请假申请在同一时间段内不会被重复处理。
  • 员工A通过OA系统提交了一个请假申请,该申请包括请假类型、起始时间、结束时间等信息。由于某种原因(如网络问题或用户快速点击提交按钮多次),该请求被发送了多次到服务器。服务器需要确保即使接收到多个相同的请求,也只处理一次。

实现思路

  • 对于服务的提供者来说,我们需要在接口中做好幂等控制,来避免因为重复而导致的脏数据
  • 对于重复提交的场景,我们可以使用token作为这个幂等号 来保障唯一性,当用户每一次访问页面的时候,都向后端接口请求获取一个token,然后在之后本页面的操作中,都需要把这个token带过来。如果页面没有刷新,这个token应该是不变的。
  • 基于token机制,每次接口请求前先获取一个token,然后再下次请求的时候在请求的header体中加上这个token,后台进行验证,如果验证通过删除token,下次请求再次判断token
  • 有了这个token就可以用它来做防重控制,并且还能避免有人恶意的刷我们的接口。
  • 在消息或者下单场景中,有了唯一的幂等字段之后,就可以基于下面三个步骤来进行幂等控制了
    • 一锁:第一步,先加锁。可以加分布式锁、或者悲观锁都可以。但是一定要是一个互斥锁!
    • 二判:第二步,进行幂等性判断。可以基于状态机、流水表、唯一性索引等等进行重复操作的判断
    • 三更新:第三步,进行数据的更新,将数据进行持久化。

redis实现自动幂等的原理图:

在这里插入图片描述

一:搭建redis的服务Api

1:首先是搭建redis服务器。

2:引入springboot中到的redis的stater,或者Spring封装的jedis也可以,后面主要用到的api就是它的set方法和exists方法,这里我们使用springboot的封装好的redisTemplate

/**
 * redis工具类
 */
@Component
public class RedisService {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 写入缓存
     * @param key
     * @param value
     * @return
     */
    public boolean set(final String key, Object value) {
        boolean result = false;
        try {
            ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
            operations.set(key, value);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }


    /**
     * 写入缓存设置时效时间
     * @param key
     * @param value
     * @return
     */
    public boolean setEx(final String key, Object value, Long expireTime) {
        boolean result = false;
        try {
            ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
            operations.set(key, value);
            redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }


    /**
     * 判断缓存中是否有对应的value
     * @param key
     * @return
     */
    public boolean exists(final String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 读取缓存
     * @param key
     * @return
     */
    public Object get(final String key) {
        Object result = null;
        ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
        result = operations.get(key);
        return result;
    }

    /**
     * 删除对应的value
     * @param key
     */
    public boolean remove(final String key) {
        if (exists(key)) {
            Boolean delete = redisTemplate.delete(key);
            return delete;
        }
        return false;

    }

}

二:自定义注解AutoIdempotent

  • 自定义一个注解,定义此注解的主要目的是把它添加在需要实现幂等的方法上,凡是某个方法注解了它,都会实现自动幂等。后台利用反射如果扫描到这个注解,就会处理这个方法实现自动幂等,使用元注解ElementType.METHOD表示它只能放在方法上,etentionPolicy.RUNTIME表示它在运行时
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoIdempotent {

}

三:token创建和检验

1:token服务接口

我们新建一个接口,创建token服务,里面主要是两个方法,一个用来创建token,一个用来验证token。创建token主要产生的是一个字符串,检验token的话主要是传达request对象,为什么要传request对象呢?主要作用就是获取header里面的token,然后检验,通过抛出的Exception来获取具体的报错信息返回给前端

public interface TokenService {

    /**
     * 创建token
     * @return
     */
    public  String createToken();

    /**
     * 检验token
     * @param request
     * @return
     */
    public boolean checkToken(HttpServletRequest request) throws Exception;

}

2:token的服务实现类

token引用了redis服务,创建token采用随机算法工具类生成随机uuid字符串,然后放入到redis中(为了防止数据的冗余保留,这里设置过期时间为10000秒,具体可视业务而定),如果放入成功,最后返回这个token值。checkToken方法就是从header中获取token到值(如果header中拿不到,就从paramter中获取),如若不存在,直接抛出异常。这个异常信息可以被拦截器捕捉到,然后返回给前端。

@Service
public class TokenServiceImpl implements TokenService {

    @Autowired
    private RedisService redisService;


    /**
     * 创建token
     *
     * @return
     */
    @Override
    public String createToken() {
        String str = RandomUtil.randomUUID();
        StrBuilder token = new StrBuilder();
        try {
            token.append(Constant.Redis.TOKEN_PREFIX).append(str);
            redisService.setEx(token.toString(), token.toString(),10000L);
            boolean notEmpty = StrUtil.isNotEmpty(token.toString());
            if (notEmpty) {
                return token.toString();
            }
        }catch (Exception ex){
            ex.printStackTrace();
        }
        return null;
    }


    /**
     * 检验token
     *
     * @param request
     * @return
     */
    @Override
    public boolean checkToken(HttpServletRequest request) throws Exception {

        String token = request.getHeader(Constant.TOKEN_NAME);
        if (StrUtil.isBlank(token)) {// header中不存在token
            token = request.getParameter(Constant.TOKEN_NAME);
            if (StrUtil.isBlank(token)) {// parameter中也不存在token
                throw new ServiceException(Constant.ResponseCode.ILLEGAL_ARGUMENT, 100);
            }
        }

        if (!redisService.exists(token)) {
            throw new ServiceException(Constant.ResponseCode.REPETITIVE_OPERATION, 200);
        }

        boolean remove = redisService.remove(token);
        if (!remove) {
            throw new ServiceException(Constant.ResponseCode.REPETITIVE_OPERATION, 200);
        }
        return true;
    }
}

四:拦截器的配置

1:web配置类,实现WebMvcConfigurerAdapter,主要作用就是添加autoIdempotentInterceptor到配置类中,这样我们到拦截器才能生效,注意使用@Configuration注解,这样在容器启动是时候就可以添加进入context中

@Configuration
public class WebConfiguration extends WebMvcConfigurerAdapter {

    @Resource
   private AutoIdempotentInterceptor autoIdempotentInterceptor;

    /**
     * 添加拦截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(autoIdempotentInterceptor);
        super.addInterceptors(registry);
    }
}

2:拦截处理器:主要的功能是拦截扫描到AutoIdempotent到注解到方法,然后调用tokenService的checkToken()方法校验token是否正确,如果捕捉到异常就将异常信息渲染成json返回给前端

/**
 * 拦截器
 */
@Component
public class AutoIdempotentInterceptor implements HandlerInterceptor {

    @Autowired
    private TokenService tokenService;

    /**
     * 预处理
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        //被ApiIdempotment标记的扫描
        AutoIdempotent methodAnnotation = method.getAnnotation(AutoIdempotent.class);
        if (methodAnnotation != null) {
            try {
                return tokenService.checkToken(request);// 幂等性校验, 校验通过则放行, 校验失败则抛出异常, 并通过统一异常处理返回友好提示
            }catch (Exception ex){
                ResultVo failedResult = ResultVo.getFailedResult(101, ex.getMessage());
                writeReturnJson(response, JSONUtil.toJsonStr(failedResult));
                throw ex;
            }
        }
        //必须返回true,否则会被拦截一切请求
        return true;
    }


    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }

    /**
     * 返回的json值
     * @param response
     * @param json
     * @throws Exception
     */
    private void writeReturnJson(HttpServletResponse response, String json) throws Exception{
        PrintWriter writer = null;
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html; charset=utf-8");
        try {
            writer = response.getWriter();
            writer.print(json);

        } catch (IOException e) {
        } finally {
            if (writer != null)
                writer.close();
        }
    }

}

五:保障业务数据的幂等性

  • 解决接口幂等问题,只需要记住一句口令"一锁、二判、三更新”,只要严格遵守这个过程,那么就可以解决并发问题。
  • 一锁:第一步,先加锁。可以加分布式锁、或者悲观锁都可以。但是一定要是一个互斥锁!
  • 二判:第二步,进行幂等性判断。可以基于状态机、流水表、唯一性索引等等进行重复操作的判断.
  • 三更新:第三步,进行数据的更新,将数据进行持久化。

注意事项:

  • 三步需要严格控制顺序,确保加锁成功后进行数据查询和判断,幂等性判断通过后再更新,更新结束后释放锁。
  • 以上操作需要有一个前提,那就是第一步加锁、和第二步判断的时候,需要有一个依据,这个就是幂等号了,通常需要和上游约定一个唯一ID作为幂等号。然后通过对幂等号加锁,再通过幂等号进行幂等判断即可。
  • 锁这个过程,建议使用Redis实现分布式锁,因为他是非阻塞的高效率的互斥锁。非常适合在幂等控制场景中。
  • 二判这个过程,如果有操作流水,建议基于操作流水做幂等,并将幂等号作为唯一性约束,确保唯一性。如果没有流水,那么基于状态机也是可以的。
  • 但是不管怎么样,数据库的唯一性约束都要加好,这是系统的最后一道防线。万一前面的锁失效了,这里也能控制
    得住不会产生脏数据。

总结

  • 本次使用springboot和拦截器、redis来优雅的实现接口幂等,对于幂等在实际的开发过程中是十分重要的,因为一个接口可能会被无数的客户端调用,如何保证其不影响后台的业务处理,如何保证其只影响数据一次是非常重要的,它可以防止产生脏数据或者乱数据,也可以减少并发量,实乃十分有益的一件事。而传统的做法是每次判断数据,这种做法不够智能化和自动化,比较麻烦。而今天的这种自动化处理也可以提升程序的伸缩性。

标签:Exception,return,WMS,OA,token,param,key,public,CRM
From: https://blog.csdn.net/qq_45736680/article/details/140408625

相关文章

  • electron loadURL加载http协议(或内网)环境下使用navigator.mediaDevices.getUserMedi
    场景我使用的electron27版本。众所周知,navigator.mediaDevices.getUserMediaAPI只能在https环境下使用,在非https环境下使用时navigator.mediaDevices会返回undefined。除了例外的这几种情况。例外的几种情况在MDN安全上下文文章中进行了说明说明了。大致意思是在https,fi......
  • Mangoa-Auth/芒果自助多应用企业级网站授权系统源码
    Mangoa_Auth多应用授权系统是专门为个人或企业开发者打造的一款自助多应用授权程序,后端基于PHP原生,前端基于EasyWeb框架,拥有域名授权、秘钥授权、IP授权,您可以根据您喜欢的方式进行授权验证,其中您还可以关闭授权验证等您想开启的时候随时开启授权验证即可,并且我们集成了独立的......
  • Android 四大组件 Activity、Service、Broadcast、Content Provider
    一、Android四大组件Activity、Service、Broadcast、ContentProvider1、Activity:1.1、打开App内部Activity:Intentintent=newIntent(SourceActivity.this,TargetActivity.class);startActivity(intent); 1.2、打开Activity并获取返回结果(类似模式对话框): 主Activit......
  • ProComponent搭建Upload表单
    背景利用ProComponent,创建一个能够上传文件的表单。一开始打算使用<BetaSchemaForm/>进行构建,但是columns中valueType不支持Upload组件,因此无法实现所以需要利用ProForm进行构建 做法因为是弹窗表单,所以需要<ModalForm></ModalForm><ModalFormformRef={formRef}layo......
  • 在windows下部署thingsBoard本地安装详细教程
    ThingsBoard是一个开源的物联网平台,用于数据收集、处理、可视化展示以及设备管理。ThingsBoard使用行业标准物联网协议(MQTT,CoAP和HTTP)实现设备连接,并支持云和本地部署。ThingsBoard结合了可扩展性,容错性和性能,因此您永远不会丢失数据。ThingsBoard提供设备和资产的管理:通过......
  • 测试人必会 K8S 操作之 Dashboard
    在云计算和微服务架构的时代,Kubernetes(K8S)已成为管理容器化应用的标准。然而,对于许多新手来说,K8S的操作和管理常常显得复杂而神秘。特别是,当你第一次接触K8SDashboard时,你是否也感到有些无所适从? K8SDashboard是Kubernetes提供的一种用户友好的图形界面工具,它让用......
  • PowerCreatorCMS UploadResourcePic 任意文件上传漏洞复现
    严正声明1.本文仅用于技术交流,目的是向相关安全人员展示漏洞的存在和利用方式,以便更好地提高网络安全意识和技术水平。2.任何人不得利用本文中的技术手段进行非法攻击和侵犯他人的隐私和财产权利。一旦发生任何违法行为,责任自负。3.本文中提到的漏洞验证poc仅用于授权......
  • keycloak~使用自定义的注册页
    添加FormAction的实现packageorg.keycloak.phone.authentication.forms;importorg.keycloak.Config;importorg.keycloak.authentication.FormAction;importorg.keycloak.authentication.FormActionFactory;importorg.keycloak.authentication.FormContext;importorg.......
  • BUUCTF刷题_RoarCTF 2019_Easy Calc
    个人刷题学习记录,如有错误请多多指教进入题目如下,猜测是命令注入,输入ls弹框,估计是做了过滤查看页面源代码,发现一串代码,但是不怎么看得懂,查看网上大佬的wp进行学习看了别人的解题步骤知道这里有个url,存在calc.php,访问一下看看是啥上面注释里面写了有waf,猜测这里应该是waf的......
  • PowerCreatorCMS UploadResourcePic 任意文件上传漏洞复现
    0x01产品简介PowerCreatorCMS是翰博尔信息技术有限公司(简称翰博尔PowerCreator)推出的一款教育资源管理平台,专注于教育领域的信息化解决方案。PowerCreatorCMS是集成了软件平台和硬件设备、多系统高度融合的教育资源管理平台。它旨在通过技术手段提升教育资源的管理、共享和......