极验验证码识别

爬虫之滑动验证码识别 一 目标 用程序来识别并通过验证码证,包括分析识别思路、识别缺口位置、生成滑块拖动路径、模拟实现滑块拼合通过证等步骤。 二 了解验证码 1 官网 https://www.geetest.com/ 2 原理 主要证方法是拖动滑块拼合图像。若图像完全拼合,则证成功,即表单成功提交,否则需要重新证。 举例:魅族登录页面采用了验证码。https://login.fly... 阅读详情

你可能曾经遇到过需要在网站登录时校验验证码的情况,这就是一种叫做“极验”的安全技术服务。极验是行为式验证安全技术服务的领先者,他们提供验证码服务,让用户在登录网站时需要输入验证码来确保安全性。如果你曾经在某些网站登录时需要校验验证码,那很有可能这个验证码就是由极验提供的。

PS:动手能力弱得朋友可以直奔www.ttocr.com进行使用。

例如某“XX”官网,它使用了极验验证码来保护用户账户的安全。一种常见的破解极验验证码的方法是计算缺口位置,使用Selenium自动化测试工具模拟人类手动拖动滑块的过程。虽然这种方法实现简单,但存在两个缺点:首先,模拟滑动容易被极验检测到自动化软件,导致滑动操作失败;其次,每次登录任务都需要驱动浏览器,登录耗时较长,不适合实时性要求较高的数据采集任务。

因此,一些人选择更艰难的方法,他们死磕JavaScript代码,破解每个请求中的加密参数,然后在程序中发送请求以获取正确响应。这种方法最直接且高效,无需等待浏览器对页面进行渲染,只要解出加密的请求参数,发送请求获取响应即可。需要注意的是,用户在浏览器中的操作最终都会转化为请求发送到服务端,服务端对请求参数进行合法性校验,并响应结果。

请求参数分析

我使用登录方法作为分析入口。我输入错误的账号密码,然后点击滑动验证码直到正确的位置。此后,页面会提示“账户名或密码错误!”。我查看浏览器的Network栏,发现登录动作实际上是发送了一个名为“accLoginPC.do”的请求。

该请求的URL为“https://xxx.xxx/webapi/accLoginPC.do”(已隐去敏感信息)。

该请求参数Form Data中包含多个参数,如“appId”、“loginName”、“loginPwd”、“geetest_challenge”等。可以看到,密码已被加密为一长串字符“CN-SA95...”,另外还有三个以“geetest_”开头的加密参数,分别为:

• “geetest_challenge: 102f7d723ad76e387ad6000f87ff91f8j3”

• “geetest_validate: 651ecdf62cb1e940e5ea999b6af7fc10”

• “geetest_seccode: 651ecdf62cb1e940e5ea999b6af7fc10|jordan”

从参数命名上,我们可以清楚地看到这是极验滑动验证码的加密参数。也就是说,我们的点击和拖动操作最终会转换为这三个加密参数。因此,我们的主要工作就是破解这三个参数。注意到,其中的“geetest_validate”和“geetest_seccode”参数基本相同,只是后者多了一个“|jordan”的字符串后缀。因此,我们的主要工作是解密“challenge”和“validate”两个参数。

对于其他非极验加密参数,如“loginPwd”、“jtSafeKey”、“token”等,这些都是XX官网自身的加密逻辑,破解难度不大。因此,在本文中,我们将重点关注极验参数的破解。

我们进一步查看,发现了一个名为“ajax.php?”的请求,其中响应信息类似于JSON格式,其中包含“validate”参数。惊讶的是,这不就是我们之前提到的“geetest_validate”参数吗?

仔细观察其请求参数,我们发现它包含“gt”、“challenge”、“lang”、“w”和“callback”等参数。其中,“w”被加密为一长串字符。

通过分析,我们逐渐理清了思路,确定了每个请求需要解密哪些参数。接下来,我们需要查看 JavaScript 代码中这些参数的加密方式,逐步解决这个问题。

然而,极验为了增加破解的难度,对代码进行了大量的混淆操作,使得代码几乎无法被理解。它采用了 Unicode 编码和大量混淆代码,这导致我们无法直接搜索关键参数名如“challenge”来找到相关代码。

因此,我们需要将完整的 JavaScript 代码复制出来,然后使用一个 Unicode 编码还原工具进行解码。解码后,我们可以将代码复制到编辑器中,搜索“challenge”参数,终于找到了几个关键的参数。

然而,这些代码仍然难以阅读,因为极验对每个关键参数都进行了混淆。它使用类似于“UtTS”这样的代码进行替换,具体的混淆逻辑藏在 JavaScript 代码中。我们需要进一步还原代码。

此外,我们还会发现一些看起来毫无意义的代码,例如“var xow_list = uklgT.xow”。经过研究,我们发现这实际上是极验加入的冗余代码,主要目的是为了混淆视听。

比如,对于这样的冗余代码:

function tPcX(e) {
    var SkB = uklgT.yaA()[0][22];

    for (; SkB !== uklgT.yaA()[16][19]; ) {
        switch (SkB) {
        case uklgT.yaA()[0][22]:

            var t = this;

            var r = e["DxJq"];

            SkB = uklgT.yaA()[0][21];

            break;

        case uklgT.yaA()[16][21]:

            r["height"] = r["width"] = 0;

            t["vjyG"] = r["getContext"]("2d");

            SkB = uklgT.yaA()[4][20];

            break;

        case uklgT.yaA()[4][20]:

            t["wOTb"] = t["xmDd"] = t["yZRm"] = t["AZ_O"] = 0;

            t["BnKG"] = r;

            SkB = uklgT.yaA()[4][19];

            break;

        }

    }

}

去冗余之后,就变成了以下精简且易读的代码:

function tPcX(e) {
    var t = this;

    var r = e["DxJq"];

    r["height"] = r["width"] = 0;

    t["vjyG"] = r["getContext"]("2d");

    t["wOTb"] = t["xmDd"] = t["yZRm"] = t["AZ_O"] = 0;

    t["BnKG"] = r;

}

我们对多个 JavaScript 文件进行反混淆和去冗余的处理后,JavaScript的调用逻辑变得更加清晰了。此外,还需要从JavaScript代码中提取出关键的代码,即用于加密请求参数的代码。这个过程称为“代码解绑定”。 这个过程并不需要太高深的技巧,需要的是耐心和细心。通过在Chrome浏览器中打断点,分析请求的入口和出口,逐步剥离出关键代码。例如,如果需要解密参数a,就需要抽离出加密参数a的代码,并封装为一个名为“get_a()”的函数。将所需的JavaScript函数封装后,在Python程序中,我们可以使用PyExecJS库方便地执行JavaScript代码,以获取加密参数。以下是一些示例代码:slide.js/fullpage.js 等。

def get_js_object(js_file_path):

    """获取js可执行对象"""

    with open(os.path.dirname(__file__) + js_file_path, encoding='GBK') as f:

        js_file = f.read()

        return execjs.compile(js_file)

pwd_encrypt_js = get_js_object(pwd_encrypt_js_path)

full_page_t1_js = get_js_object(full_page_t1_js_path)

full_page_w1_js = get_js_object(full_page_w1_js_path)

full_page_w2_js = get_js_object(full_page_w2_js_path)

u_js = get_js_object(u_js_path)

slide_u_js = get_js_object(slide_u_js_path)

slide_a_js = get_js_object(slide_a_js_path)

def get_encrypt_pwd(pwd):

    """获取加密后的密码"""

    return pwd_encrypt_js.call('pwdEncrypt', pwd)

def get_full_page_t1(s):

    """获取fullpage的t1参数"""

    return full_page_t1_js.call('get_t', s)

def get_full_page_w1(gt, challenge, s):

    """获取fullpage的w1参数"""

    t = get_full_page_t1(s)

    return full_page_w1_js.call('get_w', gt, challenge, s, t)

def get_full_page_w2(gt, challenge, s):

    """获取fullpage的w2参数"""

    return full_page_w2_js.call('get_w', gt, challenge, s)

def get_slide_w(gt, challenge, s, offset, track):

    """获取slide的w参数"""

    u = {
        'lang': 'zh-cn',

        'userresponse': u_js.call('getUserResponse', offset - 1, challenge),

        'passtime': track[-1][-1],

        'imgload': random.randint(110, 180),

        'a': u_js.call("mouse_encrypt", track),

        'ep': {"v": "1.2", "f": u_js.call("lmWn", gt + challenge)},

        'rp': u_js.call("lmWn", gt + challenge[0:32] + str(track[-1][-1]))

    }

    u = slide_u_js.call('_encrypt', u, s)

    a = slide_a_js.call('get_a', s)

    return u + a

滑动轨迹采集

在完成代码解绑定之后,还需要做一件重要的工作,就是采集滑动轨迹。在之前的代码中,函数get_slide_w()中包含有偏移量offset和轨迹track。对于偏移量offset,我们需要获取两张验证码图片,分别有和没有缺口的版本,并通过对比像素计算出偏移量。而验证码图片还需要还原,因为我们通过URL获取到的图片是经过打乱的。我们需要根据极验的JavaScript代码对这两张打乱的图片进行还原,具体还原逻辑不再赘述。对于滑动轨迹track,若使用自动化工具模拟人类拖动滑块,容易被识别为机器行为而导致滑动失败。为了解决这个问题,我采用人工手动拖动验证码的方式收集一批轨迹,构成轨迹库。之后,只要算出滑动验证码缺口的偏移量,从轨迹库中找到与偏移量相近的轨迹即可。虽然有一定的错误率,比如,轨迹库不够丰富而导致取不到偏移量对应的轨迹,但是我们可以在接下来的程序里采用一种特殊的优化方式对其进行优化,可以保证每次登录100%成功。

以上便是整个极验滑动验证码破解的过程,再简单总结一下:

请求参数分析

代码反混淆

代码解绑定

滑动轨迹采集

其中,还包括验证码图片的还原,图片缺口的计算等等...

在文章中还提到了一种特殊的优化方式,即极验Session验证码池的设计。我们可以预先滑动好验证码(实际上就是发请求),然后把得到成功响应的Session存起来,放在一个池子中。这样做的好处主要有两点:提高登录效率,缩减耗时;解耦合,提高数据采集程序的健壮性。当有登录任务到来时,我们只需要从池子中取出一个极验Session,加上登录任务中的用户名/密码(按照XX官网,密码还需做加密)直接去登录,是不是既简单又高效呢?

本文主要介绍了如何设计一个自动更新和过期删除的验证码池子。作者使用 Redis Sorted Sets(zset)数据类型来存储极验 Session,将当前时间戳作为元素的 score,zset 会根据 score 对插入元素进行排序。这样,可以使用 zrevrange 命令方便地获取最新的一个验证码 session,并使用 zremrangebyscore 移除某个时间范围内的验证码,从而达到自动过期删除的目的。

作者提到,生产极验 Session 并放入池子的示例代码如下。

import json

import pickle

import re

import time

from io import BytesIO

from threading import Thread

from PIL import Image

from requests import session

from config import gt_register_url, common_login_headers, get_php_url, ajax_php_url, prefix_url, geetest_session_key

from param import get_full_page_w1, get_full_page_w2, get_track, get_slide_w, get_s

from utils.captcha import calculate_offset

from utils.fetch import fetch

from utils.logger import logger

from utils.response import Resp

from utils.times import now_str

from utils.zk import client

class GSession:

    """获取极验Session"""

    def __init__(self):

        self.session = session()

        self.res = Resp.SUCCESS

        self.gt = str()

        self.challenge = str()

        self.s = get_s()

        self.validate = str()

        self.sec_code = str()

        self.bg_url = str()

        self.full_bg_url = str()

        self.offset = 0

        self.track = list()

    def set_gt_challenge(self) -> bool:

        """发送网络请求,拿到gt和challenge"""

        params = dict(t=now_str())

        resp = fetch(self.session, url=gt_register_url, headers=common_login_headers, params=params)

        if resp is None:

            logger.warning('无法获取gt/challenge...')

            self.res = Resp.TIMEOUT

            return False

        res = resp.json()

        logger.info('gt/challenge请求结果:{}'.format(res))

        self.gt, self.challenge = res['gt'], res['challenge']

        return True

    def get_php(self):

        """注册参数s:s经过多层加密拼接成w"""

        params = {
            'gt': self.gt,

            'challenge': self.challenge,

            'lang': 'zh-cn',

            'w': get_full_page_w1(self.gt, self.challenge, self.s),

            'callback': 'geetest_' + now_str()

        }

        resp = fetch(self.session, url=get_php_url, headers=common_login_headers, params=params)

        if resp is None:

            logger.warning('无法注册参数s...')

            self.res = Resp.TIMEOUT

        return resp is not None

    def ajax_php(self, step=1, params=None):

        """

        step=1:发送请求,校验参数w

        step=2:滑动滑块

        """

        if step == 1:

            params = {
                'gt': self.gt,

                'challenge': self.challenge,

                'lang': 'zh-cn',

                'w': get_full_page_w2(self.gt, self.challenge, self.s) + '1',

                'callback': 'geetest_' + now_str()

            }

        resp = fetch(self.session, url=ajax_php_url, headers=common_login_headers, params=params)

        if resp is None:

            self.res = Resp.TIMEOUT

            return False

        if step != 1:

            res = json.loads(re.search(r'(.∗?)
(
.

?
)
', resp.text, re.S).group(1))

            if res['data']['result'] != 'success':

                self.res = Resp.SLIDE_ERR

                return False

            self.validate = res['data']['validate']

            self.sec_code = self.validate + '|jordan'

        return True

    def get_slide_images(self):

        """获取验证码图片的地址"""

        params = {
            'is_next': 'true',

            'type': 'slide3',

            'gt': self.gt,

            'challenge': self.challenge,

            'lang': 'zh-cn',

            'https': 'true',

            'protocol': 'https://',

            'offline': 'false',

            'product': 'popup',

            'api_server': 'captcha-api.com',

            'width': '100%',

            'callback': 'geetest_' + now_str()

        }

        resp = fetch(self.session, url=get_php_url, headers=common_login_headers, params=params)

        if resp is None:

            self.res = Resp.TIMEOUT

            return False

        res = json.loads(re.search(r'(.∗?)
(
.

?
)
', resp.text, re.S).group(1))

        # 获得滑动验证码图片的URL(带缺口+不带缺口)

        self.bg_url = prefix_url + res['data']['bg']

        self.full_bg_url = prefix_url + res['data']['fullbg']

        logger.info('滑动验证码图片,bg_url:{}, full_bg_url:{}'.format(self.bg_url, self.full_bg_url))

        # 更新gt/challenge

        self.gt = res['data']['gt']

        self.challenge = res['data']['challenge']

        return True

    def get_track(self):

        """获取滑动轨迹"""

        resp1 = fetch(self.session, url=self.bg_url, headers=common_login_headers)

        resp2 = fetch(self.session, url=self.full_bg_url, headers=common_login_headers)

        if not (resp1 and resp2):

            self.res = Resp.TIMEOUT

            return False

        img1 = Image.open(BytesIO(resp1.content))

        img2 = Image.open(BytesIO(resp2.content))

        # 计算偏移量

        self.offset = calculate_offset(img1, img2)

        # 根据偏移量获取轨迹

        self.track = get_track(self.offset)

        if self.track is None:

            self.res = Resp.TRACK_ERR

        return self.track is not None

    def slide(self):

        """滑动滑块"""

        params = {
            'gt': self.gt,

            'challenge': self.challenge,

            'lang': 'zh-cn',

            'w': get_slide_w(self.gt, self.challenge, get_s(), self.offset, self.track),

            'callback': 'geetest_' + now_str()

        }

        return self.ajax_php(step=2, params=params)

    def run(self):

        self.set_gt_challenge() and self.get_php() and self.ajax_php() and self.get_slide_images() and self.get_track() and self.slide()

        logger.info('获取极验session结果:{}'.format(self.res))

        return self.res == Resp.SUCCESS

def produce_session():

    """生产极验session"""

    while True:

        gs = GSession()

        try:

            if gs.run():

                res = pickle.dumps(gs.session)

                client.zadd(geetest_session_key, {res: time.time()})

            else:

                logger.error('获取极验session请求出错')

        except Exception as e:

            logger.error('获取极验session失败,错误信息:{}'.format(e))

        time.sleep(4)

def pop_session():

    """从池子中弹出极验session"""

    res = client.zrevrange(geetest_session_key, 0, 0, withscores=True)

    if res:

        g_session, score = res[0]

        g_session = pickle.loads(g_session)

        client.zremrangebyscore(geetest_session_key, min=score, max=score)

        return g_session

    return None

if __name__ == '__main__':

    thread_list = []

    for i in range(4):

        thread = Thread(target=produce_session)

        thread.start()

        thread_list.append(thread)

    for thread in thread_list:

        thread.join()

验证码定期删除的代码如下:

登录后复制

def expire_schedule():

    """定期删除"""

    client.zremrangebyscore(geetest_session_key, 0, time.time() - 3600)

    timer = threading.Timer(2, expire_schedule)

    timer.start()

if __name__ == '__main__':

    expire_schedule()

以上,全篇完!

如果上述代码遇到问题或已更新无法使用等情况可以联系Q:1436423940或直接访问www.ttocr.com测试对接(免费得哈)
 

验证码识别例子 很多网站的登陆都有验证码一项,而的方案就是应用的非常普遍。更多的场景是在反爬虫的对抗中,验证码更是首选。本次目标则是用程序来识别并通过验证码证。本次使用的是Python库是selenium库,Chrome浏览器,并配置好ChromeDriver。验证码官网为:https://auth.geetest.com/login/。它是一个专注于提供证安全的系统,主要证方式是拖动滑块拼合图像。若图像完全拼合,则证成功,即表单成功提交,否则需要重新证,如图所示: 1.先明确识别步骤:首先, 阅读详情

相关推荐

第四代滑块验证码破解(二):滑块缺口识别

第四代滑块验证码破解(二):滑块缺口识别声明 声明 原创文章,请勿转载! 本文内容仅限于安全研究,不公开具体源码。维护网络安全,人人有责。 本文关联文章超链接:

帯泪的鱼的博客 1万+

[639]验证码破解分析&滑动验证码识别

本节我们分析并实现了验证码识别,其关键在于识别的思路,如怎样识别缺口位置,怎样生成运动轨迹等,学会了这些思路后以后我们再遇到类似原理的验证码同样可以完成识别过程。最后,放上代码,有需要的小伙伴可以自取,在使用时请注意,需要更改自己的账号密码,如果没有,则需要注册。'''验证码特点:首先点击按钮进行智能证,如果证不通过,则会弹出滑动证的窗口,拖动滑块拼合图像进行证,之后生成三个加密参数,通过表单提交到后台,后台还会进行一次证。识别证需要三步:1.模拟点击证按钮。

周小董 7516

Python 爬虫实战 滑动验证码识别

上节我们了解了图形验证码识别,简单的图形验证码我们可以直接利用 Tesserocr 来识别,但是近几年又出现了一些新型验证码,如滑动验证码,比较有代表性的就是验证码,它需要拖动拼合滑块才可以完成证,相对图形验证码来说识别难度上升了几个等级,本节来讲解下验证码识别过程。 1. 本节目标 本节我们的目标是用程序来识别并通过验证码证,其步骤有分析识别思路、识别缺口位置、生成滑块拖动路径,最后模拟实现滑块拼合通过证。 2. 准备工作 本次我们使用的 Python 库是 Seleniu

易云云博客 1757

验证码识别】—— 验证码识别

目前,许多网站采取各种各样的措施来反爬虫,其中一个措施便是使用验证码。随着技术的发展,验证码的花样越来越多。验证码最初是几个数字组合的简单的图形验证码,后来加入了英文字母和混淆曲线。有的网站还可能看到中文字符的验证码,这使得识别愈发困难。后来 12306 验证码的出现使得行为验证码开始发展起来,用过 12306 的用户肯定多少为它的验证码头疼过。我们需要识别文字,点击与文字描述相符的图片,验证码完全正确,证才能通过。

爱吃饼干的小白鼠的博客 1196

Python爬虫系列之滑动验证码识别

1. 本节目标 本节我们的目标是用程序来识别并通过验证码证,其步骤有分析识别思路、识别缺口位置、生成滑块拖动路径,最后模拟实现滑块拼合通过证。 2. 准备工作 本次我们使用的 Python 库是 Selenium,使用的浏览器为 Chrome,在此之前请确保已经正确安装好了 Selenium 库、Chrome浏览器并配置好了 ChromeDriver,相关流程可以参考第一章的说明。 3....

qq_42992919的博客 2409

滑动验证码识别教程

EC.text_to_be_present_in_element((By.CLASS_NAME, 'geetest_success_radar_tip_content'), '证成功')首先等待验证码加载完成(wait_pic),获取网页截图(get_screenshot),然后获取证背景图所在的位置及大小参数(get_position)和滑块对象(get_slider)。get_gap()方法遍历两张图片的每个像素,再利用is_pixel_equal()方法判断两张图片同一位置的像素。

asfdsgdf的博客 1022

【Python爬虫教程】进阶篇-06 滑动验证码识别

摘要:了解滑动验证码、特点、识别思路、初始化、模拟点击、识别缺口、模拟拖动。

仲君Johnny的博客 1642

使用F#实现滑动验证码识别

wait.Until(fun driver -> driver.FindElement(By.ClassName("geetest_success_radar_tip_content")).Text.Contains("证成功")) |> ignore。在这篇文章中,我们使用F#实现了滑动验证码识别。代码中使用了Selenium库来操作浏览器,通过模拟点击、获取验证码图片、计算滑动距离,并最终完成滑块拖动证。从模拟点击到识别滑动缺口、计算位移并模拟拖动滑块。识别滑动缺口的位置,计算位移。

asfdsgdf的博客 780

Spring 源码系列(23): SpringApplication.run() 启动全流程

本文深入解析了SpringApplication.run()的核心流程,通过源码拆解揭示了从启动到容器就绪的完整生命周期。文章重点分析了三个关键阶段:prepareEnvironment的配置加载逻辑(系统属性>环境变量>配置文件)、createApplicationContext的容器类型推断机制(基于classpath判断Web/Reactive/普通应用),以及callRunners的启动后回调特性(按@Order执行初始化任务)。同时澄清了常见误区,如内嵌Tomcat实际在refresh阶段的onR

不才不才不不才的博客 408

Java 并发灵魂拷问:notify/signal 唤醒顺序是规范还是实现?

本文探讨了Java中Object.notify()和Condition.signal()唤醒线程的顺序问题。关键结论如下: Object.notify(): JVM规范未规定唤醒顺序,HotSpot实现虽采用FIFO(从WaitSet头部取线程),但这属于实现细节而非规范要求 由于synchronized是非公平锁,被唤醒线程需重新竞争锁,最终执行顺序无法保证 严禁业务代码依赖该顺序 Condition.signal(): 严格按FIFO顺序唤醒(AQS等待队列头部线程) 是设计约定,业务可合理依赖该特性

weixin_68315058的博客 363

[Java]-微服务面试题

服务降级是服务自我保护的一种方式,或者保护下游服务的一种方式,用于确保服务不会受请求突增影响变得不可用,确保服务不会崩溃。微服务的负载均衡主要使用了一个组件Ribbon,比如,我们在使用feign远程调用的过程中,底层的负载均衡就是。2,我们还在skywalking设置了告警规则,特别是在项目上线以后,如果报错,我们分别设置了可以给相关负责人。简历上写的微服务,只要是发生了多个服务之间的写操作,都需要进行分布式事务控制。2,在客户端的配置文件中,可以配置某一个服务调用的负载均衡策略(局部)

. 616

Java Spring 批量修改,实体、接口、方法的定义

本文描述了一个批量更新车辆类型的接口实现。该功能通过Controller层接收包含出库单ID列表和车辆类型的请求参数,调用Service层进行处理。实现层采用事务管理,首先证参数有效性,然后查询对应出库单数据并批量更新车辆类型字段,最后返回操作结果(成功更新记录数或错误信息)。该接口主要用于快递业务中其他出库单的车辆类型批量修改操作,包含完整的参数校、异常处理和事务管理机制。

LYBWWP 55

并发 13 · 异步编排

摘要:本文探讨了异步编排在秒杀下单场景中的应用,分析了传统Future的局限性,并介绍了CompletableFuture如何通过链式调用解决任务依赖与并行问题。文章详细讲解了创建异步任务、串联任务(thenApply/thenCompose)、合并任务(thenCombine/allOf)以及异常处理等核心方法,强调使用独立线程池避免阻塞公共资源。最后结合秒杀案例,展示了如何构建高效、清晰的异步流水线,优化响应时间。全文提供代码示例和选型建议,帮助开发者实现高性能异步流程编排。

记录后端技术的深水区:MySQL、并发、JVM、分布式。原理为主,实战为辅,能少踩一个坑是一个。 403

简单聊聊 OpenFeign 框架源码

骨架:JDK 动态代理 + 方法元数据 + handler 分发,和 MyBatis 同一套路;主线:调用 → 填 RequestTemplate → 拦截器 → 负载均衡选实例 → Client 执行 → Decoder 解码,异常走 ErrorDecoder/Retryer/fallback;Spring 整合三件事扫描注册 FactoryBean、懒构建代理、给每个客户端独立配置;面试主线。

weixin_47626220的博客 385

Spring Boot 实现数据脱敏:自定义注解 + Jackson 序列化器

加了它之后,@Sensitive 就等价于 @JsonSerialize(using = SensitiveJsonSerializer.class) ,Jackson才能识别并触发我们的序列化器。Jackson默认不认识我们自定义的 @Sensitive 注解,@JacksonAnnotationsInside 的作用是:允许把 @JsonSerialize 组合进自定义注解。时,数据库里存的一直是完整值,脱敏是一个展示的动作,而存储层要保护数据应该使用。的这一层比较好,下面是具体实现。

sjkflw121150的博客 213

LangChain4J

Prompt:好比UI层,进行用户交互Langchain4j, Spring AI:好比Controller,调用AI大模型各类AI大模型:好比Service,提供人工智能服务向量数据库:好比Mapper,进行数据存储。

渔阳的博客 189

Spring配置文件

本文系统介绍了配置文件的作用、Spring Boot支持的配置文件格式及其特点。首先阐述了配置文件解决硬编码问题、实现程序灵活配置的核心价值,并以手机字体大小为例说明其应用场景。接着详细讲解了Spring Boot支持的三种配置文件格式(.properties、.yml、.yaml),包括它们的加载优先级和共存规则。最后分别深入分析了properties配置文件的键值对语法、@Value注解读取方式及其冗余信息缺点,以及yml配置文件的基本语法、引号使用规则和优缺点对比,为开发者选择合适的配置文件格式提供参

qq_71356343的博客 253

家政派单系统开发实战:架构设计与派单算法指南

家政派单系统是连接用户需求与上门服务人员的核心调度平台,其开发难点并不在于简单的CRUD,而是在于如何设计一套能支撑“多角色、多任务类型、高并发抢单”的架构,以及一套能让订单和师傅效率化的派单算法。一旦师傅手动拒绝系统派单,近1小时的接单权重下降50%,防止师傅只接高价单而忽略普通单,导致用户体受损。建议优先搭建一个可配置化的规则引擎,将派单距离、服务类目、师傅等级做成后台可调整的配置项,这样后期运营调整策略时,无需修改代码。师傅接单后的轨迹追踪,是提升用户体的关键,也是开发的难点。

zww8949111的博客 321
上一篇: 极验验证码识别
下一篇: 极验验证识别
gdf;lkgk;algl'dh
博客等级 码龄3年 0粉丝 33原创
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值