AVt天堂网 手机版,亚洲va久久久噜噜噜久久4399,天天综合亚洲色在线精品,亚洲一级Av无码毛片久久精品

當前位置:首頁 > 科技  > 軟件

Spring Boot如何優雅提高接口數據安全性

來源: 責編: 時間:2023-11-01 17:06:19 348觀看
導讀1.背景最近我司業務上需要對接第三方各大銀行平臺,調用第三方接口和提供接口供第三方調用,這時候的對外open接口安全性就得重視了,再有就是之前我在知乎上發布一篇《Spring Security實現后端接口權限驗證》的總結,有個兄

1.背景

最近我司業務上需要對接第三方各大銀行平臺,調用第三方接口和提供接口供第三方調用,這時候的對外open接口安全性就得重視了,再有就是之前我在知乎上發布一篇《Spring Security實現后端接口權限驗證》的總結,有個兄弟提出一個問題:只做接口功能菜單權限檢驗還不夠,還得做數據權限檢驗才行,舉個例子:用戶A有刪除某條數據的接口權限,這個接口的參數是傳記錄id來刪除的(ps:平時我們開發接口也是這么做的),后端執行的邏輯就是通過登錄信息通過用戶認證,然后再判斷接口菜單權限,緊接著就執行如下SQL邏輯:I6428資訊網——每日最新資訊28at.com

delete from table where id=?

這里的id就是掉接口傳遞的參數,這時候假如用戶B知道了怎么調接口,就根據id自增長的特性隨意傳id,就會刪掉別人的數據,所以這是一個嚴重的問題,要解決這問題可以像上面說的一樣加上數據權限,執行邏輯如下:I6428資訊網——每日最新資訊28at.com

delete from table where id=? and user_id = userId

這樣就避免數據被別人操作了,也就是加上了數據權限判斷,但是卻給業務邏輯增加了復雜性同時老接口業務邏輯難以適配,本質上來說web頁面上看到的數據就是根據用戶角色做過數據隔離的,可以這么理解你能看到哪些數據和你有那些功能菜單操作權限就差不多避免上面所說的情況了,但是保不準懂代碼的人使用postman等工具惡意調接口而產生上面的情況,我們還是得正視這個問題,既然通過數據權限解決該問題不太友好,那么我們可以再思考下怎么避免這個問題???這個問題可以轉換為怎么避免別人輕易就能調通接口,解決辦法就是不能在外網暴露接口信息,拒絕接口裸奔,從而有效提高接口安全性,這也是今天我們這篇總結的核心主旨。當然這里強調一下我這里說的是有效提高,不是絕對保證安全,做不到...I6428資訊網——每日最新資訊28at.com

項目推薦:基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba企業級系統架構底層框架封裝,解決業務開發時常見的非功能性需求,防止重復造輪子,方便業務快速開發和企業技術棧框架統一管理。引入組件化的思想實現高內聚低耦合并且高度可配置化,做到可插拔。嚴格控制包依賴和統一版本管理,做到最少化依賴。注重代碼規范和注釋,非常適合個人學習和企業使用I6428資訊網——每日最新資訊28at.com

Github地址:https://github.com/plasticene/plasticene-boot-starter-parentI6428資訊網——每日最新資訊28at.com

Gitee地址:https://gitee.com/plasticene3/plasticene-boot-starter-parentI6428資訊網——每日最新資訊28at.com

2.Spring Boot如何提高接口安全性

在Spring Boot項目中提高接口安全的核心所在:加密和加簽,加固接口參數、驗證復雜度。I6428資訊網——每日最新資訊28at.com

加密:對參數進行加密傳輸,拒絕接口參數直接暴露,這樣就可以有效做到防止別人輕易準確地獲取到接口參數定義和傳參格式要求了。I6428資訊網——每日最新資訊28at.com

加簽:對接口參數進行加簽,可以有效防止接口參數被篡改和接口參數被重放惡刷。I6428資訊網——每日最新資訊28at.com

2.1 加密

現今有許許多多的加密算法,這里就不對算法進行過度敘述,畢竟不是我們今天的主題,但是加密算法大體分為非對稱加密和對稱加密。I6428資訊網——每日最新資訊28at.com

非對稱加密

非對稱加密算法是一種密鑰的保密方法。非對稱加密算法需要兩個密鑰:公開密鑰(publickey:簡稱公鑰)和私有密鑰(privatekey:簡稱私鑰)。公鑰與私鑰是一對,如果用公鑰對數據進行加密,只有用對應的私鑰才能解密。因為加密和解密使用的是兩個不同的密鑰,所以這種算法叫作非對稱加密算法。I6428資訊網——每日最新資訊28at.com

對稱加密

加密秘鑰和解密秘鑰是一樣,當你的密鑰被別人知道后,就沒有秘密可言了。I6428資訊網——每日最新資訊28at.com

經過需求分析和科學借鑒我們采用了非對稱加密算法RSA和對稱加密算法AES來完成接口加密。至于這兩種加密算法的原理與實現有興趣自己去查資料,我這里就說一下選它們的原因:I6428資訊網——每日最新資訊28at.com

AES 是對稱加密算法,優點:加密速度快;缺點:如果秘鑰丟失,就容易解密密文,安全性相對比較差I6428資訊網——每日最新資訊28at.com

RSA 是非對稱加密算法 , 優點:安全 ;缺點:加密速度慢I6428資訊網——每日最新資訊28at.com

接口參數加解密的流程大致如圖所示:I6428資訊網——每日最新資訊28at.com

圖片圖片I6428資訊網——每日最新資訊28at.com

具體步驟如下:I6428資訊網——每日最新資訊28at.com

  1. 客戶端(調用接口方)隨機生成AES加解密的密鑰aes key,這里的AES密鑰每次調接口都需要隨機生成,可以有效提高安全性。
  2. 使用aes key對接口參數requestBody進行加密,data=base64(AES(json參數))
  3. 通過RSA加密算法加密aes key,有效保證aes算法的密鑰的可靠安全性  key=base64(RSA(aes key))
  4. 經過上面的步驟,得到了加密后的業務參數及密鑰,這時候就可以發送請求調用接口了
  5. 服務端接收到請求之后,先通過RSA算法對key進行解密獲取到ase key, 再通過aes key解密data得到真正json參數,最后映射到接口方法的參數對象上,供controller的業務方法邏輯使用。
  6. 業務方法執行完成后,對響應參數進行加密,加密流程和上面的1、2、3一樣
  7. 客戶端收到響應參數之后,和步驟5一樣解密響應參數,就拿到了真正的數據結果了。

2.2 加簽

簽名驗證也是當下提高接口安全性主要措施之一,核心就是客戶端在調用接口時按照一定規則生成簽名sign,服務端拿到簽名sign之后進行驗證操作,大致流程如下:I6428資訊網——每日最新資訊28at.com

圖片圖片I6428資訊網——每日最新資訊28at.com

具體步驟:I6428資訊網——每日最新資訊28at.com

  1. 對請求參數對象bean轉sortMap保證參數拼接的有序性,如果接口沒有參數也沒有關系,這里轉成一個空的sortMap
  2. 按照約定拼接生成字符串content = sortMap + nonce + timestamp
  3. 使?SHA1WithRSA算法及私鑰對concent進?簽名sign
  4. 服務端判斷timestamp是否超過簽名有效期和nonce是否重復使用
  5. 服務端和步驟2一樣規則生成字符串content
  6. 使?SHA1WithRSA算法及公鑰對concent和sign進行驗簽

3.優雅實現接口加密、加簽

在實現這個需求時,考慮到全公司的多個團隊開發使用的通用性和便捷性,所以我們對加密、加簽操作進行了公共的抽取封裝,同時通過一個注解@ApiSecurity來標識接口是否需要進行加密、加簽操作,在業務側極大程度地降低了開發使用成本,不用寫冗余代碼,做到了真正的優雅。I6428資訊網——每日最新資訊28at.com

@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE, ElementType.METHOD})@Documentedpublic @interface ApiSecurity {    @Alias("isSign")    boolean value() default true;    /**     * 是否加簽驗證,默認開啟     * @return     */    @Alias("value")    boolean isSign() default true;    /**     * 接口請求參數是否需要解密     * @return     */    boolean decryptRequest() default false;    /**     * 接口響應參數是否需要加密     * @return     */    boolean encryptResponse() default false;}

這里注解屬性可以看到簽名驗證默認是開啟的,因為我們認為接口安全性加簽是必須的,至于參數加解密可以視情況而定,通過屬性配置開關,做到了極致的靈活性,這也是優雅呀。I6428資訊網——每日最新資訊28at.com

使用案例:下面就是一個需要加密加簽的接口I6428資訊網——每日最新資訊28at.com

@PostMapping("/security")    @ApiSecurity(encryptResponse = true, decryptRequest = true)    public User testApiSecurity(@RequestBody User user) {        System.out.println(user);        return user;    }

可以看到我們在項目業務服務中只需要@ApiSecurity就可以了,就是這么簡單,至于怎么實現的下面我們就來看看。I6428資訊網——每日最新資訊28at.com

為了全公司對接口加密、加簽功能實現統一和規范,我們將實現抽取,封裝集成在公司自定義的web starter中,這樣只要項目服務引入這個starter依賴就可以使用該功能了I6428資訊網——每日最新資訊28at.com

首先我們對加密傳輸的參數bean進行規定封裝如下:I6428資訊網——每日最新資訊28at.com

@Datapublic class ApiSecurityParam {    /**     * 應用id     */    private String appId;    /**     * RSA加密后的aes秘鑰,需解密     */    private String key;    /**     * AES加密的json參數     */    private String data;    /**     * 簽名     */    private String sign;    /**     * 時間戳     */    private String timestamp;    /**     * 請求唯一標識     */    private String nonce;}

等于說加密、加簽的參數格式,調用方需按照上面的對象傳參,當然為了提高拓展性,簽名的相關信息sign、timestamp、nonce可以放到請求的header里面,也能獲取到。拿到apiSecurityParam我們就可以進行請求參數解密、驗簽了,需要通過判斷是否使用了注解@ApiSecuriy來決定是否執行請求參數解密、驗簽邏輯,這就正好可以使用基于注解的切面實現啦,在說切面之前,先說說一次接口請求requestBody的輸入流InputStream只能讀取一次,就是說request.getInputStream()只能使用一次,原因如下:I6428資訊網——每日最新資訊28at.com

因為流對應的是數據,數據放在內存中,有的是部分放在內存中。read 一次標記一次當前位置(mark position),第二次read就從標記位置繼續讀(從內存中copy)數據。 所以這就是為什么讀了一次第二次是空了。 怎么讓它不為空呢?只要inputstream 中的pos 變成0就可以重寫讀取當前內存中的數據。javaAPI中有一個方法public void reset() 這個方法就是可以重置pos為起始位置,但是不是所有的IO讀取流都可以調用該方法!ServletInputStream是不能調用reset方法,這就導致了只能調用一次getInputStream()。I6428資訊網——每日最新資訊28at.com

而我們需要先讀取出requestBody進行解密,然后拿到解密之前的參數映射到真正的接口方法參數對象里,所以必須解決這個問題。I6428資訊網——每日最新資訊28at.com

解決方法就是原始的HttpServletRequest的InputStream只能讀取一下,那么我們就重新自定義封裝一個HttpServletRequest可以實現多次讀取。I6428資訊網——每日最新資訊28at.com

public class RequestBodyWrapper extends HttpServletRequestWrapper {    //用于將流保存下來    private String body;    public RequestBodyWrapper(HttpServletRequest request) throws IOException {        super(request);        body = new String(StreamUtils.copyToByteArray(request.getInputStream()), StandardCharsets.UTF_8);    }    /**     * 重寫getInputStream, 從body中獲取請求參數     * @return     * @throws IOException     */    @Override    public ServletInputStream getInputStream() throws IOException {        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes("UTF-8"));        ServletInputStream servletInputStream = new ServletInputStream() {            @Override            public boolean isFinished() {                return false;            }            @Override            public boolean isReady() {                return false;            }            @Override            public void setReadListener(ReadListener readListener) {            }            @Override            public int read() throws IOException {                return byteArrayInputStream.read();            }        };        return servletInputStream;    }    @Override    public BufferedReader getReader() throws IOException {        return new BufferedReader(new InputStreamReader(getInputStream()));    }    public String getBody() {        return this.body;    }    public void setBody(String body) {        this.body = body;    }}

然后通過一個過濾器filter把自定義封裝的RqequestBodyWapper傳遞下去:I6428資訊網——每日最新資訊28at.com

@Slf4jpublic class BodyTransferFilter implements Filter {    @Override    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {        RequestBodyWrapper requestBodyWrapper = null;        try {            HttpServletRequest req = (HttpServletRequest)request;            requestBodyWrapper = new RequestBodyWrapper(req);        }catch (Exception e){            log.warn("requestBodyWrapper Error:", e);        }        chain.doFilter((Objects.isNull(requestBodyWrapper) ? request : requestBodyWrapper), response);    }}

接下來就可以來看看切面了:這里是解析請求參數和驗簽和邏輯所在:I6428資訊網——每日最新資訊28at.com

@Aspect@Slf4j@Order(value = OrderConstant.AOP_API_DECRYPT)public class ApiSecurityAspect {    @Resource    private ApiSecurityProperties apiSecurityProperties;    @Resource    private StringRedisTemplate stringRedisTemplate;    private static final String NONCE_KEY = "x-nonce-";    @Pointcut("execution(* com.plasticene..controller..*(..)) && " +            "(@annotation(com.plasticene.boot.web.core.anno.ApiSecurity) ||" +            " @target(com.plasticene.boot.web.core.anno.ApiSecurity))")    public void securityPointcut(){}    @Around("securityPointcut()")    public Object aroundApiSecurity(ProceedingJoinPoint joinPoint) throws Throwable {        //=======AOP解密切面通知=======        ApiSecurity apiSecurity = getApiSecurity(joinPoint);        boolean isSign = apiSecurity.isSign();        boolean decryptRequest = apiSecurity.decryptRequest();        // 獲取request加密傳遞的參數        HttpServletRequest request = getRequest();        // 只能針對post接口的請求參數requestBody進行統一加解密和加簽,這是規定        if (!Objects.equals("POST", request.getMethod())) {            throw new BizException("只能POST接口才能加密加簽操作");        }        // 獲取controller接口方法定義的參數        Object[] args = joinPoint.getArgs();        Object[] newArgs = args;        ApiSecurityParam apiSecurityParam = new ApiSecurityParam();        // 請求參數解密        if (decryptRequest) {            // 不支持多個請求,因為解密請求參數之后會json字符串,再根據請求參數的類型映射過去,如果有多個參數就不知道映射關系了            if (args.length > 1) {                throw new BizException("加密接口方法只支持一個參數,請修改");            }            // args.length=0沒有請求參數,就說明沒必要解密,因為接口壓根不接收參數,即使使用者無腦開啟的該接口的參數加密,這里不做任何邏輯即可            if (args.length == 1) {                RequestBodyWrapper requestBodyWrapper;                if (request instanceof RequestBodyWrapper) {                    requestBodyWrapper = (RequestBodyWrapper) request;                } else {                    requestBodyWrapper = new RequestBodyWrapper(request);                }                String body = requestBodyWrapper.getBody();                apiSecurityParam = JSONObject.parseObject(body, ApiSecurityParam.class);                // 通過RSA私鑰解密獲取到aes秘鑰                String aesKey = RSAUtil.decryptByPrivateKey(apiSecurityParam.getKey(), apiSecurityProperties.getRsaPrivateKey());                // 通過aes秘鑰解密data參數數據                String data = AESUtil.decrypt(apiSecurityParam.getData(), aesKey);                //獲取接口入參的類                Class<?> c = args[0].getClass();                //將獲取解密后的真實參數,封裝到接口入參的類中                Object o = JSONObject.parseObject(data, c);                newArgs = new Object[]{o};            }        }        // 驗簽        if (isSign) {            verifySign(request, newArgs.length == 0 ? null : newArgs[0], apiSecurityParam);        }        return joinPoint.proceed(newArgs);    }    void verifySign(HttpServletRequest request, Object o, ApiSecurityParam apiSecurityParam) {        // 如果請求參數是加密傳輸的,那就先從ApiSecurityParam獲取簽名和時間戳等等。        // 如果請求參數不是加密傳輸的,那么ApiSecurityParam的字段取值都為null,這時候在請求的header里面獲取參數信息        String sign = apiSecurityParam.getSign();        if (StringUtils.isBlank(sign)) {            sign = request.getHeader("X-Sign");        }        if (StringUtils.isBlank(sign)) {            throw new BizException("簽名不能為空");        }        String nonce = apiSecurityParam.getNonce();        if (StringUtils.isBlank(nonce)) {            nonce = request.getHeader("X-Nonce");        }        if (StringUtils.isBlank(nonce)) {            throw new BizException("唯一標識不能為空");        }        String timestamp = apiSecurityParam.getTimestamp();        Long t;        if (StringUtils.isBlank(timestamp)) {            timestamp = request.getHeader("X-Timestamp");        }        if (StringUtils.isBlank(timestamp)) {            throw new BizException("時間戳不能為空");        } else {            try {                t = Long.valueOf(timestamp);            } catch (Exception e) {                throw new BizException("非法的時間戳");            }        }        // 判斷timestamp時間戳與當前時間是否超過簽名有效時長(過期時間根據業務情況進行配置),如果超過了就提示簽名過期        long now = System.currentTimeMillis() / 1000;        if (now - t > apiSecurityProperties.getValidTime()) {            throw new BizException("簽名已過期");        }        // 判斷nonce        boolean nonceExists = stringRedisTemplate.hasKey(NONCE_KEY + nonce);        if (nonceExists) {            //請求重復            throw new BizException("唯一標識nonce已存在");        }        // 驗簽        SortedMap sortedMap = SignUtil.beanToMap(o);        String content = SignUtil.getContent(sortedMap, nonce, timestamp);        boolean flag = RSAUtil.verifySignByPublicKey(content, sign, apiSecurityProperties.getRsaPublicKey());        if (!flag) {            throw new BizException("簽名驗證不通過");        }        stringRedisTemplate.opsForValue().set(NONCE_KEY+ nonce, "1", apiSecurityProperties.getValidTime(),                TimeUnit.SECONDS);    }    private HttpServletRequest getRequest() {        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();        HttpServletRequest request = requestAttributes.getRequest();        return request;    }    private ApiSecurity getApiSecurity(JoinPoint joinPoint) {        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();        Method method = methodSignature.getMethod();        ApiSecurity apiSecurity = method.getAnnotation(ApiSecurity.class);        if (Objects.isNull(apiSecurity)) {            apiSecurity = method.getDeclaringClass().getAnnotation(ApiSecurity.class);        }        return apiSecurity;    }}

這代碼沒什么好講的了,就按照上面的加密、加簽流程圖邏輯實現的,而且注釋也很清楚,可以自己慢慢消化,這里面涉及的工具類如RSAUtil、AESUtil、SignUtil等,礙于文章代碼篇幅,我就這里就在一一展示,我會在文章后面放上全部代碼的項目github地址以供下載的。I6428資訊網——每日最新資訊28at.com

上面的切面只完成了接口參數的解密和驗簽,至于對響應參數的加密返回我們放到了ResponseBodyAdvice中實現。I6428資訊網——每日最新資訊28at.com

@RestControllerAdvice@Slf4jpublic class ResponseResultBodyAdvice implements ResponseBodyAdvice<Object> {    @Resource    private ObjectMapper objectMapper;    @Resource    private ApiSecurityProperties apiSecurityProperties;    /**     * 判斷類或者方法是否使用了 @ResponseResultBody     */    @Override    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {        return AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseResultBody.class)                || returnType.hasMethodAnnotation(ResponseResultBody.class)                || AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ApiSecurity.class)                || returnType.hasMethodAnnotation(ApiSecurity.class);    }    /**     * 當類或者方法使用了 @ResponseResultBody 就會調用這個方法     * 如果返回類型是string,那么springmvc是直接返回的,此時需要手動轉化為json     * 因為當body都為null時,下面的非加密下的if判斷參數類型的條件都不滿足,如果接口返回類似為String,     * 會報錯com.shepherd.fast.global.ResponseVO cannot be cast to java.lang.String     */    @SneakyThrows    @Override    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {        Method method = returnType.getMethod();        Class<?> returnClass = method.getReturnType();        Boolean enable = apiSecurityProperties.getEnable();        ApiSecurity apiSecurity = method.getAnnotation(ApiSecurity.class);        if (Objects.isNull(apiSecurity)) {            apiSecurity = method.getDeclaringClass().getAnnotation(ApiSecurity.class);        }        if (enable && Objects.nonNull(apiSecurity) && apiSecurity.encryptResponse() && Objects.nonNull(body)) {            // 只需要加密返回data數據內容            if (body instanceof ResponseVO) {                body = ((ResponseVO) body).getData();            }            JSONObject jsonObject = encryptResponse(body);            body = jsonObject;        } else {            if (body instanceof String || Objects.equals(returnClass, String.class)) {                String value = objectMapper.writeValueAsString(ResponseVO.success(body));                return value;            }            // 防止重復包裹的問題出現            if (body instanceof ResponseVO) {                return body;            }        }        return ResponseVO.success(body);    }    JSONObject encryptResponse(Object result) {        String aseKey = AESUtil.generateAESKey();        String content = JSONObject.toJSONString(result);        String data = AESUtil.encrypt(content, aseKey);        String key = RSAUtil.encryptByPublicKey(aseKey, apiSecurityProperties.getRsaPublicKey());        JSONObject jsonObject = new JSONObject();        jsonObject.put("key", key);        jsonObject.put("data", data);        return jsonObject;    }}

這里就是對接口返回參數格式進行統一ResponseVO,同時判斷是否需要進行返回參數加密執行相應邏輯即可。I6428資訊網——每日最新資訊28at.com

4.總結

至此,對于Spring Boot如何提高接口安全性的思路與實現已講完,同時我們也盡量進行了抽取封裝,做到了極致的優雅實現。當然這里還是要再次強調一下以上的思路實現是不能絕對保證接口安全性的,只能做到”防君子不妨小人“,可以這么說假如不做加密加簽這些保護措施,黑客破解接口就會不費吹灰之力,自己就經歷過我在個人的阿里云服務器部署的MySQL服務,為了訪問簡單省事,我直接在外網暴露了3306端口,同時用戶名密碼都是root,就被黑客黑了勒索比特幣。I6428資訊網——每日最新資訊28at.com

圖片圖片I6428資訊網——每日最新資訊28at.com

幸好里面數據不是很重要,我后面把暴露端口映射成其他的了,密碼也改了一個復雜的,我看你再給我破解了~~~。說這些我就想表達這里的加密、加簽就是為了加大破解接口的復雜度,有了加密、加簽保障想破解不費九牛二虎之力是不行的。I6428資訊網——每日最新資訊28at.com

本文轉載自微信公眾號「Shepherd進階筆記」,可以通過以下二維碼關注。轉載本文請聯系公眾號。I6428資訊網——每日最新資訊28at.com

I6428資訊網——每日最新資訊28at.com

本文鏈接:http://www.tebozhan.com/showinfo-26-16410-0.htmlSpring Boot如何優雅提高接口數據安全性

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: 解密MySQL索引原理與優化策略:Java開發者必讀

下一篇: 權限管理——多系統下的數據權限通用控制

標簽:
  • 熱門焦點
  • JavaScript 混淆及反混淆代碼工具

    介紹在我們開始學習反混淆之前,我們首先要了解一下代碼混淆。如果不了解代碼是如何混淆的,我們可能無法成功對代碼進行反混淆,尤其是使用自定義混淆器對其進行混淆時。什么是混
  • Rust中的高吞吐量流處理

    作者 | Noz編譯 | 王瑞平本篇文章主要介紹了Rust中流處理的概念、方法和優化。作者不僅介紹了流處理的基本概念以及Rust中常用的流處理庫,還使用這些庫實現了一個流處理程序
  • 分享六款相見恨晚的PPT模版網站, 祝你做出精美的PPT!

    1、OfficePLUSOfficePLUS網站旨在為全球Office用戶提供豐富的高品質原創PPT模板、實用文檔、數據圖表及個性化定制服務。優點:OfficePLUS是微軟官方網站,囊括PPT模板、Word模
  • 零售大模型“干中學”,攀爬數字化珠峰

    文/侯煜編輯/cc來源/華爾街科技眼對于絕大多數登山愛好者而言,攀爬珠穆朗瑪峰可謂終極目標。攀登珠峰的商業路線有兩條,一是尼泊爾境內的南坡路線,一是中國境內的北坡路線。相
  • 小紅書1周漲粉49W+,我總結了小白可以用的N條漲粉筆記

    作者:黃河懂運營一條性教育視頻,被54萬人&ldquo;珍藏&rdquo;是什么體驗?最近,情感博主@公主是用鮮花做的,火了!僅僅憑借一條視頻,光小紅書就有超過128萬人,為她瘋狂點贊!更瘋狂的是,這
  • ESG的面子與里子

    來源 | 光子星球撰文 | 吳坤諺編輯 | 吳先之三伏大幕拉起,各地高溫預警不絕,但處于厄爾尼諾大&ldquo;烤&rdquo;之下的除了眾生,還有各大企業發布的ESG報告。ESG是&ldquo;環境保
  • AMD的AI芯片轉單給三星可能性不大 與臺積電已合作至2nm制程

    據 DIGITIMES 消息,英偉達 AI GPU 出貨逐季飆升,接下來 AMD MI 300 系列將在第 4 季底量產。而半導體業內人士表示,近日傳出 AMD 的 AI 芯片將轉單給
  • 7月4日見!iQOO 11S官宣:“雞血版”驍龍8 Gen2+200W快充加持

    上半年已接近尾聲,截至目前各大品牌旗下的頂級旗艦都已悉數亮相,而下半年即將推出的頂級旗艦已經成為了數碼圈爆料的主流,其中就包括全新的iQOO 11S系
  • iQOO Neo8 Pro評測:旗艦雙芯加持 最強性能游戲旗艦

    【Techweb評測】去年10月,iQOO推出了一款Neo7手機,該機搭載了聯發科天璣9000+,配備獨顯芯片Pro+,帶來了同價位段最佳的游戲體驗,一經上市便受到了諸多用
Top