支付系统设计:收银台设计二

news2025/1/12 0:02:18

文章目录

  • 前言
  • 1. 接口校验
    • 1.1 Chains
    • 1.2 Checker
      • 1.2.1 AbstractChecker
      • 1.2.2 TokenChecker
      • 1.2.3 OrderChecker
      • 1.2.4 UserInfoChecker
      • 1.2.5 BaseInfoChecker
      • 1.2.6 SignChecker
    • 1.3 ApiFilter
  • 2. 下单
  • 3. 收银台首页
    • 2.1 OrderInfoResolver
    • 2.2 UserBaseInfoResolver
  • 4. 执行流程
  • 总结


前言

在这里插入图片描述
本篇将讲解下单以及拉起收银台加载收银台页面时的代码实现(1/4/5/7/8/9步骤),本篇偏向代码实现。


1. 接口校验

1.1 Chains

api:
  security:
    enabled: true
    timestampMilliseconds: 600000
    securityChains:
      - /syt/user/auth=none
      - /syt/user/auth/new=none
      - /syt/payment/mode=token
      - /syt/**=token
      - /cashier/v2/home=order,user,base,sign
      - /cashier/v2/payment/**=order,user,base,sign
      - /cashier/v2/coupon/list=order,user,base,sign
      - /withdraw/home=withdraw,user,sign
      - /withdraw/confirm=withdraw,user,sign
      ... ...

cashierapi系统为业务系统和收银台前端系统提供了很多接口,这些接口所要检验的内容也有所不同,所以,将接口校验规则组成不同的Chains,配置到YML中。

如上:/cashier/v2/home=order,user,base,sign标识加载收银台首页需要进行订单校验、用户信息校验、终端校验、签名校验。

1.2 Checker

接口,主要定义了一些常量以及构建CheckerChain 的方法:

/**
 * @author Kkk
 * @Describe: Checker接口
 */
public interface Checker{

    String USERDETAILS_KEY = "USER_BASE_INFO";
    String APPLICATION_INFO_KEY = "BASE_PARAM_INFO";
    String SECRET_KEY = "SECRET_KEY";
    String ORDER_INFO_KEY = "ORDER_INFO";
    String UNIQUE_ID = "UNIQUE_ID";

    CheckResult PASSED = new CheckResult(200,null,null);
    CheckResult UNAUTHORIZED = new CheckResult(401,null,null);
    CheckResult FORBIDDEN = new CheckResult(403,null,null);

    CheckResult check(HttpServletRequest request, HttpServletResponse response);

    class CheckerChain implements Checker {

        private Checker checker;
        private CheckerChain chain;

        public CheckerChain(Iterator<Checker> iterator) {
            this.checker = iterator.next();
            if (iterator.hasNext()) {
                this.chain = new CheckerChain(iterator);
            }
        }

        public CheckResult check(HttpServletRequest request, HttpServletResponse response) {
            CheckResult result = checker.check(request, response);
            if (result.isSucess() && chain != null) {
                return chain.check(request, response);
            } else {
                response.setStatus(result.getStatus());
                return result;
            }
        }
    }
    /**
     * 校验结果
     */
    class CheckResult {
        private int status;
        private String code;
        private String message;

        public CheckResult(int status, String code, String message) {
            this.status = status;
            this.code = code;
            this.message = message;
        }
        ... ...
    }
}

1.2.1 AbstractChecker

抽象层,主要定义一些从HttpServletRequest 头中获取指定参数的方法:

/**
 * @author Kkk
 * @Describe: Checker抽象层
 */
public abstract class AbstractChecker implements Checker{

    static CheckResult E_SYS = new CheckResult(700, ErrorEnum.ERR_SYSTEM);

    protected SecurityProperties securityProperties;

    public AbstractChecker(SecurityProperties securityProperties) {
        this.securityProperties = securityProperties;
    }

    protected String getFromHeaderOrParameter(HttpServletRequest request, String key) {
        if (request.getParameterMap().containsKey(key)) {
            return request.getParameter(key);
        } else {
            return request.getHeader(key);
        }
    }

    protected String getFromHeader(HttpServletRequest request, String key) {
        return request.getHeader(key);
    }
    protected String getRequestPath(HttpServletRequest request) {
        String url = request.getServletPath();

        if (request.getPathInfo() != null) {
            url += request.getPathInfo();
        }

        return url;
    }
    public String getToken(HttpServletRequest request){
        return getFromHeader(request, securityProperties.getToken());
    }

    public String getSign(HttpServletRequest request){
        return getFromHeaderOrParameter(request,securityProperties.getSign());
    }

    public String getRequestId(HttpServletRequest request){
        return getFromHeader(request, securityProperties.getRequestId());
    }
    public String getSourceInfo(HttpServletRequest request){
        return getFromHeader(request,securityProperties.getSourceInfo());
    }
    ... ...
}

1.2.2 TokenChecker

进行Token校验:

/**
 * @author Kkk
 * @Describe: TokenChecker
 */
public class TokenChecker extends AbstractChecker {
    private static Checker.CheckResult E_TOKEN = new CheckResult(701, ErrorEnum.API_TOKEN);

    private IUserService userService;
    private ITokenService tokenService;

    @Override
    public CheckResult check(HttpServletRequest request, HttpServletResponse response) {
        String token = getToken(request);
        String sourceInfo = getSourceInfo(request);
        if(StringUtils.isNotBlank(sourceInfo)) {
            try {
                sourceInfo = URLDecoder.decode(sourceInfo, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
        log.info("==> The request {},appKey:{},sourceInfo:{}",getRequestPath(request),getApplication(request),sourceInfo);
        if (StringUtils.isBlank(token)) {
            log.warn("==> The request {} from IP {}, token is null.", getRequestPath(request), IPUtil.getClientIP(request));
            return E_TOKEN;
        }
        //验证token是否有效
        String secretKey;
        try {
            secretKey  = tokenService.tokenCheck(token);
        }catch (Exception e){
            return E_TOKEN;
        }
        log.debug("token获取的secretKey为:{}",secretKey);
        if(StringUtils.isBlank(secretKey)){
            return E_TOKEN;
        }
        UserBaseInfo userBaseInfo;
        try {
            userBaseInfo = userService.getUserBaseInfoByToken(token);
            log.debug("用户基本信息:{}", MaskUtils.toJson(userBaseInfo));
        }catch (Exception e) {
            return E_TOKEN;
        }
        if (userBaseInfo == null) {
            log.warn("==> The request {} from {}, token {} not found.", getRequestPath(request), IPUtil.getClientIP(request), token);
            return E_TOKEN;
        }
        log.info("==> The request {}{}", getRequestPath(request),MaskUtils.maskMobile(userBaseInfo.getMobile()));
        request.setAttribute(USERDETAILS_KEY, userBaseInfo);
        request.setAttribute(SECRET_KEY,secretKey);
        return PASSED;
    }
}

1.2.3 OrderChecker

校验根据HttpServletRequest 头中获取到的Token(下单时返回的accessToken)能否获取到订单信息:

/**
 * @author Kkk
 * @Describe: OrderChecker
 */
public class OrderChecker extends AbstractChecker {
    private static Checker.CheckResult E_TOKEN = new CheckResult(701, ErrorEnum.API_TOKEN);

    private ITokenService tokenService;

    @Override
    public CheckResult check(HttpServletRequest request, HttpServletResponse response) {
        String sourceInfo = getSourceInfo(request);
        String accessToken = this.getToken(request);
        if (StringUtils.isBlank(accessToken)) {
            return E_TOKEN;
        }
        logger.info("==> The request {},appKey:{},token:{},sourceInfo:{},ip:{},requestId:{}",
                getRequestPath(request), getApplication(request), accessToken
                , sourceInfo, IPUtil.getClientIP(request), getRequestId(request));
        OrderInfo orderInfo;
        try {
            orderInfo = tokenService.getOrderInfo(accessToken);
        } catch (Exception e) {
            logger.error("request:{},来源IP:{},查询orderInfo异常:{}", getRequestPath(request), IPUtil.getClientIP(request), e.getMessage());
            return E_TOKEN;
        }
        request.setAttribute(ORDER_INFO_KEY, orderInfo);
        request.setAttribute(SECRET_KEY, orderInfo.getSecretKey());
        request.setAttribute(UNIQUE_ID, orderInfo.getUniqueId());
        return PASSED;
    }
}

并将orderInfo以及其中的自动SecretKey、UniqueId都存入到request中,供后面的CheckerResolver获取:

   request.setAttribute(ORDER_INFO_KEY, orderInfo);
   request.setAttribute(SECRET_KEY, orderInfo.getSecretKey());
   request.setAttribute(UNIQUE_ID, orderInfo.getUniqueId());

1.2.4 UserInfoChecker

校验根据OrderChecker获取到的UniqueId校验用户信息:

OrderChecker根据Token获取到订单信息并去除其中的UniqueId信息存入到HttpServletRequest,此处就可以从Request中获取到UniqueId信息了,然后调用客户管理系统获取并校验用户信息了。

request.setAttribute(UNIQUE_ID, orderInfo.getUniqueId());

/**
 * @author Kkk
 * @Describe: UserInfoChecker
 */
public class UserInfoChecker extends AbstractChecker {

    private IUserService userService;

    @Override
    public CheckResult check(HttpServletRequest request, HttpServletResponse response) {
        String uniqueId = request.getAttribute(UNIQUE_ID).toString();
        UserBaseInfo userBaseInfo;
        try {
            userBaseInfo = userService.getUserBaseInfoByUniqueId(uniqueId);
        }catch (Exception e){
            e.printStackTrace();
            logger.info("调用cmc系统异常,{}",e.getMessage());
            return E_SYS;
        }
        logger.info("==> The request {}{},orderInfo:{}", getRequestPath(request), MaskUtils.maskMobile(userBaseInfo.getMobile())
                , JsonUtil.toJson(request.getAttribute(ORDER_INFO_KEY)));
        request.setAttribute(USERDETAILS_KEY,userBaseInfo);
        return PASSED;
    }
}

OrderChecker 一样的套路将userBaseInfo存入到request中:

 request.setAttribute(USERDETAILS_KEY,userBaseInfo);

1.2.5 BaseInfoChecker

校验客户端信息,同样是从HttpServletRequest中取出OrderInfo ,从OrderInfo中取出applicationKey ,查询对应系统进行校验:

/**
 * @author Kkk
 * @Describe: BaseInfoChecker
 */
public class BaseInfoChecker extends AbstractChecker {

    private IApiApplicationService apiApplicationService;

    @Override
    public CheckResult check(HttpServletRequest request, HttpServletResponse response) {
        OrderInfo orderInfo = (OrderInfo) request.getAttribute(ORDER_INFO_KEY);
        String applicationKey = orderInfo.getAppKey();
        if (StringUtils.isBlank(applicationKey)) {
            logger.warn("==> The request {} from {}, appKey is null.", getRequestPath(request), IPUtil.getClientIP(request));
            return E_SYS;
        }
        ApiApplication apiApplication;
        try {
            apiApplication = apiApplicationService.getApiApplication(applicationKey);
            if (apiApplication == null) {
                logger.error("==> The request {} from {}, appKey: {} is not found.", getRequestPath(request), IPUtil.getClientIP(request), applicationKey);
                return E_SYS;
            }
        } catch (Exception e) {
            String message = String.format("==> The request %s from %s, get appKey: %s faild.", getRequestPath(request), IPUtil.getClientIP(request), applicationKey);
            logger.error(message, e);
            return E_SYS;
        }

        BaseRequest baseRequest = convertToBaseRequest(apiApplication);
        baseRequest.setIp(IPUtil.getClientIP(request));
        request.setAttribute(APPLICATION_INFO_KEY,baseRequest);
        return PASSED;
    }
}

OrderChecker 一样的套路将baseRequest存入到request中:

  request.setAttribute(APPLICATION_INFO_KEY,baseRequest);

1.2.6 SignChecker

首先从HttpServletRequest头部取出需要参加验签的字段,然后拼接,取出在OrderChecker根据Token获取到的订单信息中的存入到request中的SecretKey,进行验签:

/**
 * @author Kkk
 * @Describe: SignChecker
 */
public class SignChecker extends AbstractChecker {
    private static CheckResult E_SIGN = new CheckResult(701, ErrorEnum.API_SIGN);
    private static CheckResult E_REATTACK = new CheckResult(703, ErrorEnum.ERR_REATTACK);

    private JedisCluster jedisCluster;

    private String[] headerKeys = new String[2];

    @Override
    public CheckResult check(HttpServletRequest request, HttpServletResponse response) {
         ..... .....
        return PASSED;
    }
}

1.3 ApiFilter

构建Filter,在过滤器中执行CheckerChain

 @Bean
 @Order(2)
 public Filter apiFilter() {
     SecurityChecker securityChecker = new SecurityChecker(securityProperties.isEnabled(), securityProperties.getSecurityChains());

     List<TerminalConfig> configs = JsonUtil.jsonToGenericObject(terminalConfig, new TypeReference<List<TerminalConfig>>() {});

     securityChecker.addChecker("sign", new SignChecker(securityProperties,jedisCluster));
     securityChecker.addChecker("token",	new TokenChecker(securityProperties, userService, tokenService));
     securityChecker.addChecker("order",	new OrderChecker(securityProperties, tokenService));
     securityChecker.addChecker("user",	new UserInfoChecker(securityProperties, userService));
     securityChecker.addChecker("base",	new BaseInfoChecker(securityProperties, apiApplicationService));
     securityChecker.addChecker("caller",new CallerChecker(securityProperties,configs));
     securityChecker.addChecker("withdraw",new WithdrawOrderChecker(tokenService,securityProperties));
     ... ...
     return new ApiSecurityFilter(securityChecker);
 }

SecurityChecker中构建MatchChecker,以达到根据请求路径执行对应的Checker:

/**
 * @author Kkk
 * @Describe: SecurityChecker
 */
public class SecurityChecker implements Checker {
    private static final String NONE = "none";
    private static Checker none = new Checker() {
        @Override
        public CheckResult check(HttpServletRequest request, HttpServletResponse response) {
            return PASSED;
        }
    };

    private boolean enabled;
    private List<String> securityChains = new ArrayList<>();
    private Map<String, Checker> checkers = new HashMap<>();
    private List<MatchChecker> matchCheckers = new ArrayList<>();

    public SecurityChecker(boolean enabled, List<String> securityChains) {
        this.enabled = enabled;
        this.securityChains = securityChains;
    }

    public SecurityChecker addChecker(String name, Checker checker) {
        checkers.put(name, checker);
        return this;
    }
    ... ...
    public void init() {
        logger.info("===> Init security chains {}", securityChains);

        for (String securityPattern : securityChains) {
            String[] sp = StringUtils.splitPreserveAllTokens(securityPattern, "=");

            matchCheckers.add(new MatchChecker(PathMatcher.create(sp[0]), parse(sp[1])));
        }
    }

    @Override
    public CheckResult check(HttpServletRequest request, HttpServletResponse response) {
        String path = getRequestPath(request);

        for (MatchChecker mc : matchCheckers) {
            if (mc.matcher.matches(path)) {
                return mc.checker.check(request, response);
            }
        }

        logger.info("===> Security checker none for path: {}", path);

        return PASSED;
    }

    private String getRequestPath(HttpServletRequest request) {
        String url = request.getServletPath();

        if (request.getPathInfo() != null) {
            url += request.getPathInfo();
        }

        return url;
    }

    private Checker parse(String strCheckers) {
        if (StringUtils.isBlank(strCheckers)) {
            return none;
        }

        List<Checker> cs = new ArrayList<Checker>();
        for (String name : strCheckers.trim().split(",")) {
            if (NONE.equals(name)) {
                cs.add(none);
            } else if (!checkers.containsKey(name)) {
                throw new CashierException(ErrorEnum.ERR_PARAM, String.format("Security checker name:%s not support.", name));
            } else {
                cs.add(this.checkers.get(name));
            }
        }

        return cs.isEmpty() ? none : new CheckerChain(cs.iterator());
    }

    private static class MatchChecker {
        public PathMatcher.Matcher matcher;
        public Checker checker;

        public MatchChecker(PathMatcher.Matcher matcher, Checker checker) {
            this.matcher = matcher;
            this.checker = checker;
        }
    }
}

到此我们就完成了下单以及加载收银台首页需要进行的校验了,

#下单需要校验的Checker
- /syt/**=token
#拉起收银台需要校验的Checker
- /cashier/v2/home=order,user,base,sign

2. 下单

业务系统首先调用cashierapi系统进行下单:

    @RequestMapping(value = "/auth/payment",method = RequestMethod.POST)
    public AuthenResultVO payment(@RequestBody PaymentRequestVO requestVO){
        return cashierService.payment(requestVO);
    }

PaymentRequestVO 即为下单对象,传入到系统后创建订单并存入Redis,同时生成token、secretKey。

   ... ...
   String token = SytUtil.getUUID();
   String secretKey = SytUtil.getSecretKey(16);
   ... ...
   OrderInfo orderInfo = BeanUtil.copyProperties(request, OrderInfo.class);
   orderInfo.setSecretKey(secretKey);
   orderInfo.setToken(token);
   redisService.setex(key, RedisSettings.EXPIRE_ACCESS_TOKEN_SALT, token);
   redisService.setex(token, RedisSettings.EXPIRE_ACCESS_TOKEN_SALT, JsonUtil.toJson(orderInfo));
   return new AuthenResultVO(request.getUniqueId(), token, secretKey, RedisSettings.EXPIRE_ACCESS_TOKEN_SALT);

AuthenResultVO 响应对象:

/**
 * @author  Kkk
 * @Describe: 主动支付下单响应VO
 */
public class AuthenResultVO {
    /**
     * 用户唯一标识
     */
    private String uniqueId;

    /**
     * 准入令牌
     */
    private String accessToken;

    /**
     * 秘钥
     */
    private String secretKey;

    /**
     * 令牌剩余时间
     */
    private Integer expireSeconds;
    ... ...  
}

待业务系统发起支付拉起收银台时加载收银台页面只需要传入Token就能获取到下单时的原订单信息了。

3. 收银台首页

/**
 * @author Kkk
 * @Describe: 首页模块
 */
@Api(value = "首页模块",description = "首页模块",produces="application/json")
@RequestMapping("/cashier")
@RestController
public class HomeController {

    @Autowired
    private IHomeService homeService;

    @ApiOperation("获取标准收银台首页数据")
    @RequestMapping(value = "/v2/home", method = RequestMethod.GET)
    public HomeInfoVO getHomeInfo(UserBaseInfo userBaseInfo, OrderInfo orderInfo, BaseRequest baseRequest) {
        return homeService.getHomeInfo(userBaseInfo, orderInfo, baseRequest);
    }

    @ApiOperation("获取免登陆收银台首页数据")
    @RequestMapping(value = "/v3/home", method = RequestMethod.GET)
    public HomeInfoVO getHomeInfoNoLanding(UserBaseInfo userBaseInfo, OrderInfo orderInfo, BaseRequest baseRequest) {
        return homeService.getHomeInfo(userBaseInfo, orderInfo, baseRequest);
    }
}

当看到接口中入参如下,是不是感觉好像不对,但是又说不出来什么?

getHomeInfo(UserBaseInfo userBaseInfo, OrderInfo orderInfo, BaseRequest baseRequest)

缺少@RequestBody,那么我们就为其加上,但是不是加注解,而是使用HandlerMethodArgumentResolver方式:

2.1 OrderInfoResolver

将SecurityChecker中添加到HttpServletRequest的参数取出来对应到getHomeInfo中对应的参数中:

/**
 * @author Kkk
 * @Describe: OrderInfoResolver
 */
public class OrderInfoResolver implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return OrderInfo.class.equals(parameter.getParameterType());
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        return webRequest.getNativeRequest(HttpServletRequest.class).getAttribute(SecurityChecker.ORDER_INFO_KEY);
    }
}

2.2 UserBaseInfoResolver

一样的套路:略

4. 执行流程

通过一个图稍微总结下大概执行流程:
在这里插入图片描述
收银台总体来说是比较简单的,没什么复杂场景。


总结

拙技蒙斧正,不胜雀跃。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/436328.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

企业宁愿花 15K 重新招人,也不愿意花 10K 留住老测试员?

金三银四即将进入尾声&#xff0c;大家逐渐收敛了跳槽涨薪的想法&#xff0c;准备收收心等待过年。不置可否&#xff0c;年后必定又是一波跳槽季&#xff0c;通过跳槽才能涨薪已经成为了不少求职者内心默认的定理。 不知道什么时候开始&#xff0c;公司不能满足加薪的要求&…

leetcode 812. 最大三角形面积

题目 给你一个由 X-Y 平面上的点组成的数组 points &#xff0c;其中 points[i] [xi, yi] 。从其中取任意三个不同的点组成三角形&#xff0c;返回能组成的最大三角形的面积。与真实值误差在 10-5 内的答案将会视为正确答案。 示例 1&#xff1a; 输入&#xff1a;points [[…

手把手带你实现通讯录C语言

通讯录大家都很熟悉了&#xff0c;一个联系人包括姓名&#xff0c;年龄&#xff0c;性别&#xff0c;电话&#xff0c;地址等&#xff1b; 那我们想一想我们所学的数据类型&#xff08;int,float,double等都是单一的相同类型 &#xff08;属于内置类型&#xff09;&#xff09;…

#详细介绍!!! 造成死锁的原因以及解决方案!

本篇主要是介绍什么是死锁&#xff0c;已经死锁产生的原因&#xff0c;如果避免死锁。根据上述的几个问题让我们来阅读本篇文章。 目录 1. 什么是死锁 2. 形成死锁的原因&#xff08;四个必要条件&#xff09; 3. 如果有效避免死锁 1. 什么是死锁 死锁主要是锁彼此间进行锁等…

FPGA实现JPEG-LS图像压缩,有损无损可配置,提供工程源码和技术支持

目录 1、前言2、JPEG-LS图像压缩理论3、JPEG-LS图像压缩性能介绍4、JPEG-LS图像压缩时序介绍5、JPEG-LS图像压缩输出压缩流6、工程源码和仿真7、福利&#xff1a;工程代码的获取 1、前言 JPEG-LS&#xff08;简称JLS&#xff09;是一种无损/有损的图像压缩算法&#xff0c;其无…

1 Go语言开发环境搭建详细教程【Go语言教程】

Go语言开发环境搭建【Win、Linux、Mac】 1 SDK下载 官网地址&#xff1a;golang.org,因为一些原因国内可能无法访问。可以使用下面第二个链接。国内地址访问&#xff1a;https://golang.google.cn/dl或者https://www.golangtc.com/download 根据自己操作系统版本&#xff0c;下…

30天学会《Streamlit》(9)

30天学会《Streamlit》是一项编码挑战&#xff0c;旨在帮助您开始构建Streamlit应用程序。特别是&#xff0c;您将能够&#xff1a; 为构建Streamlit应用程序设置编码环境 构建您的第一个Streamlit应用程序 了解用于Streamlit应用程序的所有很棒的输入/输出小部件 第9天 - …

超高精度PID控制器的特殊功能(4)——分程控制功能及其应用

摘要&#xff1a;分程控制作为一种典型的复杂控制方法之一&#xff0c;常用于聚合反应工艺、冷热循环浴、TEC半导体温度控制、动态平衡法的真空和压力控制等领域。为快速和便捷的使用分程控制&#xff0c;避免采用PLC时存在的控制精度差和使用门槛高等问题&#xff0c;本文介绍…

pyqt6+vtk

这里用PyQt6vtk9.2.6 pip install PyQt6 pyqt6-tools vtk这里拉了一个水平布局 然后水平布局中加入QWidget&#xff0c;object name改为vtkWidget 右键Promote Widgets Promoted class name: QVTKRenderWindowInteractor Header file: vtkmodules.qt.QVTKRenderWindowInterac…

文章改写神器在线-AI续写文章生成器

AI续写生成器 AI续写生成器是一种利用人工智能技术的创意工具&#xff0c;能够提高写作效率&#xff0c;为营销推广带来全新的可能性。无论你是写手、广告人员还是市场营销人员&#xff0c;这个工具都能够有效地解决你在写作中遇到的难题。 在内容创作行业中&#xff0c;原创…

Amazon Linux2部署安装Jenkins

先决条件 服务器配置要求 256 MB of RAM 1 GB of drive space (although 10 GB is a recommended minimum if running Jenkins as a Docker container) 需要部署安装JDK环境部署安装的Jenkins版本为Version 2.400 部署安装JDK 1. 下载JDK软件包 wget https://corretto.aws/…

Altium Designer借助嘉立创添加PCB封装和3D模型

目录 引言打开立创专业版EDA&#xff0c;建立项目从立创商城找到器件编码添加PCB封装导出和修改3D封装 引言 由于使用Altium Designer的频率并不是特别高&#xff0c;所以每一次使用总是得东跌西撞的才回忆起一些使用步骤。因此&#xff0c;想在这里记录一下Altium Designer借…

【mysql】binlog日志

目录 1.1 基本说明1.2 binlog日志格式1.3 binlog日志查看1.4 binlog日志删除1.5 binlog操作示例 1.1 基本说明 1.全称binary log&#xff0c;二进制日志 2.记录了所有的DDL语句&#xff08;Data Definition Language数据定义语言&#xff09;和DML语句&#xff08;Data Manipul…

Java多线程中sleep()方法和wait()方法的区别

目录 具体而言 &#xff0c;sleep&#xff08;&#xff09;方法与wait&#xff08;&#xff09;方法的区别主要表现在以下几个方面&#xff1a; 引申&#xff1a;sleep&#xff08;&#xff09;方法和yield&#xff08;&#xff09;方法有什么区别&#xff1f; 常见面试题&a…

如何衡量算法的效率?时间复杂度空间复杂度

本篇博客会讲解如何衡量一个算法的效率。衡量算法的效率&#xff0c;主要有2个维度&#xff0c;分别是&#xff1a;时间复杂度和空间复杂度。 时间复杂度用来衡量算法的时间效率。时间复杂度越低&#xff0c;算法的耗时越短&#xff0c;效率则越高。空间复杂度用来衡量算法的空…

Space Cloud updated Crack

Space Cloud updated Crack Space Git Flow-对于实践连续发布周期的开发团队来说&#xff0c;在协调团队成员、建立流程和集成多个工具时&#xff0c;建立交付流可能需要付出巨大努力。为了更容易做到这一点&#xff0c;引入了JetBrains Space Git流&#xff0c;这是一个完整的…

同一热卖商品的高并发写难题 - Lua脚本扣减库存方案

目录 一、Mysql高并发写时的行锁难题 二、Redis的相关命令 1. WATCH命令 2. EVAL与EVALSHA命令 三、SpringBoot执行Lua脚本代码示例 1. 依赖包 2. Lua脚本sku.lua 3. 加载Lua脚本 4. 执行Lua脚本 四、参考资料 一、Mysql高并发写时的行锁难题 通常来说&#xff0c;秒…

百度大模型ERNIE3.0

大模型对比 文心全景图 ERNIE 3.0

《Java8实战》第9章 重构、测试和调试

9.1 为改善可读性和灵活性重构代码 Lambda 表达式可以帮助我们用更紧凑的方式描述程序的行为。 9.1.1 改善代码的可读性 可读性非常主观&#xff0c;但是通俗的理解就是“别人理解这段代码的难易程度”。改善可读性意味着你要确保你的代码能非常容易地被包括自己在内的所有…