有关软件开发编程知识及学习心得
4月9
随着 Java 生态的演进,我们将核心项目从 JDK 8 迁移至 JDK 21。然而,在升级过程中,我们遇到了一个令人费解的现象:原本在 JDK 8 上运行正常的第三方接口调用,在 JDK 21 上却频繁抛出 SSLHandshakeException: Received fatal alert: handshake_failure。本文将详细复盘这次排查过程,揭示 JDK 21 在 SSL/TLS 握手层面的底层变化,并提供终极解决方案。
一、 故障现象:版本升级后的“断连”
在将开发环境切换至 JDK 21 后,我们的应用在调用特定政府网站接口(https://www.xxx.gov.cn)时发生了故障。
错误日志:
javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
诡异现象:
JDK 8: 运行完美,无任何报错。
JDK 21: 死活连不上,且不报证书错误(CertificateException),直接握手失败。
浏览器/Postman: 可以正常访问。
这种“代码没变,环境变了”的差异,直接指向了 JDK 版本间的底层安全策略差异。
二、 排查之路:层层递进的“破案”过程
为了定位问题,我们开启 -Djavax.net.debug=ssl:handshake 调试模式,通过分析握手日志,我们经历了三个阶段的认知升级。
第一阶段:误判为“弱加密算法” —— 方案失效
直觉: JDK 21 更安全,默认禁用了弱算法。我们猜测是服务器使用了 MD5 或 1024 位密钥。
行动: 修改 java.security 文件,删除 jdk.tls.disabledAlgorithms 中的限制,甚至加上了 ECDH(误以为是禁用了 ECDH 导致的)。
结果: 无效。 即使放宽了所有限制,握手依然失败。这说明问题不在“禁用列表”,而在“协商过程”。
第二阶段:聚焦“椭圆曲线” —— 关键线索
在仔细比对 ClientHello(客户端发起)和 ServerHello(服务器回复)的日志时,我们发现了异常。
在服务器的 ECDHServerKeyExchange 消息中,出现了如下关键字段:

"ECDH ServerKeyExchange": {
  "parameters": {
    "named group": "x448"  // 服务器强制要求使用 x448 曲线
  }
}
7月17
当你还在一个词一个词地调教 Prompt 时,硅谷的大佬们已经在布局上下文工程(Context Engineering)了。

推特上的 AI 架构师 @HeyNina101 一针见血地指出:「提示词工程是给爱好者玩的,上下文工程(Context Engineering)才是专业生产力。」这条推文迅速引爆了硅谷技术圈,Shopify CEO Tobi Lutke 和前特斯拉 AI 总监 Andrej Karpathy 等大佬纷纷点赞。

Prompt vs 上下文

你在对话框里敲下的指令,就是 Prompt。

但一个能打的 AI 应用,需要的远不止 Prompt。还需要为模型构建整个世界观,这就是「上下文工程」。

上下文工程包括:

任务设定:明确告诉 AI 要干嘛
案例示范:给它几个栗子学学
外部文档:挂载知识库,让它变身专家
对话历史:让 AI 拥有短期记忆
工具输出:调用 API 后的结果反馈
系统状态:应用当前的运行情况
长期记忆:跨越对话周期的记忆
信息压缩:在有限的上下文中高效传递信息
多模态:不止文本,还有图片、声音等等

上下文给的太少,AI 就是人工智障;给的太多,系统就运行变慢、或者烧光你的预算。如何拿捏好这个平衡,不仅仅是简单的指令艺术,更是一个系统设计的挑战。

真正的LLM应用,远不止套壳

一个生产级的 LLM 应用,不仅仅是上面这些,还需要:

控制流:设计 AI 的思考和行动路径
上下文打包:智能筛选、组合信息喂给模型
模型路由:根据任务,自动切换最合适的模型
UI 与生成验证:确保输出的内容能看、能用
安全护栏、评估、预取、并行计算...

这个复杂的系统,我们称之为 LLM 软件工程,而「上下文」正是这个新世界的核心层。

到底是谁在控制 LLM?

Google DeepMind 的工程师 Philipp Schmid 用一张图给出了答案:真正决定 LLM 行为的,是一个由指令、用户输入、历史、文档、工具等共同构成的庞大上下文系统。

所以,别再只盯着那个小小的输入框了。

你认为上下文工程会是下一个风口吗?
独立开发者如何抓住这个机会?
评论区聊聊你的看法
点击在新窗口中浏览此图片点击在新窗口中浏览此图片点击在新窗口中浏览此图片点击在新窗口中浏览此图片

7月17
本篇文章深入分析了大型模型微调的基本理念和多样化技术,细致介绍了LoRA、适配器调整(Adapter Tuning)、前缀调整(Prefix Tuning)等多个微调方法。详细讨论了每一种策略的基本原则、主要优点以及适宜应用场景,使得读者可以依据特定的应用要求和计算资源限制,挑选最适合的微调方案。

一、大型模型微调的基础理论
大型语言模型(LLM)的训练过程通常分为两大阶段:

阶段一:预训练阶段
在这个阶段,大型模型会在大规模的无标签数据集上接受训练,目标是使模型掌握语言的统计特征和基础知识。此期间,模型将掌握词汇的含义、句子的构造规则以及文本的基本信息和上下文。
需特别指出,预训练实质上是一种无监督学习过程。完成预训练的模型,亦即基座模型(Base Model),拥有了普遍适用的预测能力。例如,GLM-130B模型、OpenAI的四个主要模型均属于基座模型。

阶段二:微调阶段
预训练完成的模型接下来会在针对性的任务数据集上接受更进一步的训练。这一阶段主要涉及对模型权重的细微调整,使其更好地适配具体任务。最终形成的模型将具备不同的能力,如gpt code系列、gpt text系列、ChatGLM-6B等。

那么,何为大型模型微调?
直观上,大型模型微调即是向模型“输入”更多信息,对模型的特定功能进行“优化”,通过输入特定领域的数据集,使模型学习该领域知识,从而优化大模型在特定领域的NLP任务中的表现,如情感分析、实体识别、文本分类、对话生成等。


为何微调至关重要?
其核心理由是,微调能够“装备”大模型以更精细化的功能,例如整合本地知识库进行搜索、针对特定领域问题构建问答系统等。
以VisualGLM为例,作为一个通用多模态模型,当应用于医学影像判别时,就需要输入医学影像领域的数据集以进行微调,以此提升模型在医学影像图像识别方面的表现。
这与机器学习模型的超参数优化类似,只有在调整超参数后,模型才能更好地适应当前数据集;同时,大型模型可以经历多轮微调,每次微调都是对模型能力的优化,即我们可以在现有的、已经具备一定能力的大模型基础上进一步进行微调。
5月29

Elasticsearch拼音分词

10:57编程杂谈  From: 本站原创
elasticsearch的分词器对于文本分析至关重要。对于中文等语言,合适的分词器可以显著提高搜索相关性和结果的准确性。拼音分词器不仅支持基于拼音的搜索,还能实现拼音自动补全等功能。本文将介绍如何在Elasticsearch中安装拼音分词器,以及如何配置和测试它。

分词器的三要素
在Elasticsearch中,分词器(Analyzer)由以下三个主要部分组成:

Character Filters(字符过滤器):在Tokenizer处理之前对文本进行预处理,如删除或替换特定字符。
Tokenizer(分词器):按照一定的规则将文本切分成词条(Term),例如ik_max_word就是智能切分中文。
Token Filters(词条过滤器):对Tokenizer输出的词条进行进一步处理,如转换为小写、同义词替换、拼音转换等。
安装拼音分词器插件
步骤1:下载插件
访问elasticsearch-analysis-pinyin 下载页面,下载与您的Elasticsearch版本相匹配的插件。

步骤2:上传插件
将下载的插件压缩包上传到Elasticsearch的plugins目录。

步骤3:检查插件列表
curl -XGET "http://localhost:9200/_cat/plugins?v"
预期输出:
name        component       version
node-master analysis-pinyin 7.17.6
包含 analysis-pinyin 插件。

步骤4:测试插件
curl -XPOST "http://localhost:9200/_analyze" -H "Content-Type: application/json" -d'
{
  "analyzer": "pinyin",
  "text": "姜军"
}'
预期输出:
{
  "tokens": [
    { "token": "a", "position": 0 },
    { "token": "li", "position": 1 },
    { "token": "ba", "position": 2 },
    { "token": "ba", "position": 3 }
  ]
}

Easy-Es 中使用 pinyin 分词器
确保实体类中正确引用 Analyzer.PINYIN:
@IndexField(
    fieldType = FieldType.TEXT,
    analyzer = Analyzer.PINYIN,
    searchAnalyzer = Analyzer.PINYIN,
    fieldData = true
)
private String name;
12月4
配置证书
打开CMD输入命令:

keytool -genkey -alias heck -keyalg RSA -keysize 2048 -storetype PKCS12 -keystore heck-ssl-key.p12 -validity 3650

证书会下载到当前目录下:
server:
  port: 443
  ssl:
    # 证书的路径,可用绝对路径,如果放到项目资源文件路径需要添加 classpath:
    key-store: classpath:heck-ssl-key.p12
    key-store-password: 123456
    key-store-type: PKCS12


可能还会报异常DerInputStream.getLength(): lengthTag=111, too big.  
Could not load key store 'classpath:heck-ssl-key.p12':


可以pom.xml 把这个文件加入编译,

<plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <version>3.2.0</version>
                <configuration>
                    <nonFilteredFileExtensions>
                        <!--<nonFilteredFileExtension>p12</nonFilteredFileExtension>-->
                        <nonFilteredFileExtension>jks</nonFilteredFileExtension>
                    </nonFilteredFileExtensions>
                </configuration>
            </plugin>
        </plugins>

刷新 Maven,clean 后重新 compile。

10月22
Linxu开机出现 Generating "/run/initramfs/rdsosreport.txt"解决方案
点击在新窗口中浏览此图片

解决:
一、找这个-root结尾的文件也不一样。

大家可以用ls /dev/mapper查看到自己装的镜像对应的以-root结尾的文件是哪个。
点击在新窗口中浏览此图片

二、所以我们运行的是:xfs_repair /dev/mapper/cl-root
点击在新窗口中浏览此图片
还是报错的话需要

需要卸载掉 (会丢数据,没办法,总比重做系统方便吧)
3月12
IDEA的Service窗口误删服务(也就是点到了Hide Configuration选项)后如何恢复

看下到底是做了什么操作导致的服务被隐藏  (赶时间直接到最后看解决方案就行)
正常情况下, 在开启窗口的情况下, 会显示我们所有的微服务启动类, 如下图
点击在新窗口中浏览此图片


idea在service窗口中显示多个服务如下:在这里插入图片描述
.idea > workspace.xml 中找到 RunDashboard 替换成如下


  <component name="RunDashboard">
    <option name="configurationTypes">
      <set>
        <option value="SpringBootApplicationConfigurationType" />
      </set>
    </option>
    <option name="ruleStates">
      <list>
        <RuleState>
          <option name="name" value="ConfigurationTypeDashboardGroupingRule" />
        </RuleState>
        <RuleState>
          <option name="name" value="StatusDashboardGroupingRule" />
        </RuleState>
      </list>
    </option>
  </component>
11月23
用 security加redis写项目时一直报这个错误,说是没有set方法?我用的data注解怎么可能

这个bug找了一下午,终于找到原因了:redis序列化会查询所有以get和set开头的方法,而我的user继承了security的UserDetails,多了一个集合类型的getAuthorities方法,所有导致无法序列化,使用的是jackson的序列化器

解决办法:
加上@JsonIgnore注解设置不序列化

@JsonIgnore
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        ArrayList<NdpAuthority> grantedAuthorities = CollectionUtil.newArrayList();
        if (ObjUtil.isNotEmpty(roles)) {
            roles.forEach(dict -> {
                String roleName = dict.getStr(CommonConstant.NAME);
                NdpAuthority ndpAuthority = new NdpAuthority(roleName);
                grantedAuthorities.add(ndpAuthority);
            });
        }
        return grantedAuthorities;
    }

加个属性,写个对应的set方法
11月23
在实际项目开发过程中,我们有时候需要让项目在启动时执行特定方法。如要实现这些功能:
提前加载相应的数据到缓存中;
检查当前项目运行环境;
检查程序授权信息,若未授权则不能使用后续功能;
执行某个特定方法;

一直想整理一下Springboot启动后自执行某段代码或者方法相关的点,囿于时间问题及担心理解不是太全面所以没整理,后来一想先整理出来,将这一方面的东西记录下来。其实这篇文章不应该体现spring boot,因为自动执行方式不仅限于spring boot,java spring细化到bean的初始化都可以完成。

一、java自身的启动时加载方式

1、static代码块
static静态代码块,在类加载的时候即自动执行。 由于静态代码块没有名字,我们并不能主动调用,它会在类加载的时候,自动执行。所以静态代码块,可以更早的给类中的静态属性,进行初始化赋值操作。并且,静态代码块只会自动被执行一次,因为JVM在一次运行中,对一个类只会加载一次!

2、构造函数constructor
在对象初始化时执行。执行顺序在static静态代码块之后。

3、@PostConstruct注解
@PostConstruct
public void testInit() {
    System.out.printin("postConstruct“);
}
@PostConstruct注解使用在方法上,这个方法在对象依赖注入初始化之后执行。执行节点在BeanPostProcessor的postProcessBeforeInitialization之后,在postProcessAfterInitialization之前。
很多人以为该注解是Spring提供的。其实是Java自己的注解。Java中该注解的说明:@PostConstruct该注解被用来修饰一个非静态的void()方法。被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。
注意:加了postconstruct注解的方法,如果执行失败,整个程序会无法正常启动!这个方法执行不完,整个程序也启动不了!也不建议将耗时逻辑放到这里面来。

二、Servlet加载的方式
1、实现ServletContextListener接口contextInitialized方法
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

@Slf4j
@Component
public class ServletContextListenerImpl implements ServletContextListener {
    /**
     * 在初始化Web应用程序中的任何过滤器或Servlet之前,将通知所有ServletContextListener上下文初始化。
     */
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        log.info("启动时自动执行 ServletContextListener 的 contextInitialized 方法");
    }
}
注意:该方法会在填充完普通Bean的属性,但是还没有进行Bean的初始化之前执行

三、Spring启动时加载方式
1、实现ServletContextAware接口setServletContext 方法
@Component
public class StartInitServletContext implements ServletContextAware {

    @Override
    public void setServletContext(ServletContext servletContext) {
        System.out.println("StartInitServletContext: 开始处理事情");
    }
}

2、ApplicationListener监听器
创建Listener的方式有两种:
方式1:编程实现ApplicationListener接口
方式2:使用@EventListener注解
注意监听的事件,通常是ApplicationStartedEvent 或者ApplicationReadyEvent,其他的事件可能无法注入bean。

编程实现ApplicationListener接口
/**
* 监听的是 ApplicationStartedEvent 事件,
* 则 ApplicationListener 一定会在 CommandLineRunner 和 ApplicationRunner 之前执行;
*/
@Component
public class StartInitStartedEvent implements ApplicationListener<ApplicationStartedEvent> {
    @Override
    public void onApplicationEvent(ApplicationStartedEvent event) {
        System.out.println("StartInitStartedEvent:开始处理事情");
    }
}
/**
* 监听的是 ApplicationReadyEvent 事件,
* 则 ApplicationListener 一定会在 CommandLineRunner 和 ApplicationRunner 之后执行;
*/
@Component
public class StartInitReadyEvent implements ApplicationListener<ApplicationReadyEvent> {
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        System.out.println("StartInitReadyEvent: 开始处理事情");
    }
}
实现 public interface ApplicationListener<E extends ApplicationEvent> 接口,监听 ApplicationEvent 及其下面的子事件:
/**
* 事件监听器一般是由开发者自己定义
* 定义事件监听器
*/
@Component
//@Lazy
public class MyApplicationListener implements ApplicationListener{

  @Override
  public void onApplicationEvent(ApplicationEvent event) {
    event = (PayloadApplicationEvent)event;
    System.out.println(((PayloadApplicationEvent<?>) event).getPayload());
  }
}

@EventListener方式
将要执行的方法所在的类交个Spring容器扫描(@Component),并且在要执行的方法上添加@EventListener注解执行
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class EventListenerTest {
    @EventListener
    public void onApplicationEvent(ContextRefreshedEvent event) {
        log.info("启动时自动执行  @EventListener 注解方法");
    }
}

3、InitializingBean接口
@Component
@Data
public class TestInit implements InitializingBean,eanFactoryAware,ApplicationContextAware{
    private BeanFactory beanFactory:
    private ApplicationContext applicationContext;
    private String name;
@Override
public voidafterPropertiesSet(hrows Exception {
    System.out.printin("InIt heck");
}
}
Spring为bean提供了两种初始化bean的方式,实现InitializingBean接口,实现afterPropertiesSet方法,或者在配置文件中通过init-method指定,两种方式可以同时使用。
实现InitializingBean接口是直接调用afterPropertiesSet方法,比通过反射调用init-method指定的方法效率要高一点,但是init-method方式消除了对spring的依赖。先调用afterPropertieSet()方法,然后再调用init-method中指定的方法。

4、ApplicationRunner和CommandLineRunner
SpringBoot提供了两个接口来实现Spring容器启动完成后执行的功能,两个接口分别为CommandLineRunner和ApplicationRunner。这两个接口需要实现一个run方法,将代码在run中实现即可。这两个接口功能基本一致,其区别在于run方法的入参。ApplicationRunner的run方法入参为ApplicationArguments,为CommandLineRunner的run方法入参为String数组。
10月11
${ew.customSqlSegment} 会直接在前面添加 where

@Select(select * from a ${ew.customSqlSegment})
List<a> getHeck(@Param(Constants.WRAPPER)QueryWrapper queryWrapper)

${ew.sqlSegment} 就只有条件语句

@Select(select * from a where ${ew.sqlSegment})
List<a> getHeck(@Param(Constants.WRAPPER)QueryWrapper queryWrapper)

${ew.sqlSelect} 就是 queryWrapper.select(****) 你所定义需要查询的字段

@Select(select ${ew.sqlSelect} from a )
List<a> getHeck(@Param(Constants.WRAPPER)QueryWrapper queryWrapper)
分页: 1/20 第一页 1 2 3 4 5 6 7 8 9 10 下页 最后页 [ 显示模式: 摘要 | 列表 ]