AMC Spring Boot Starter:????????????
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
liuser 5ad087edf0 release 1.0.1: SSE client-gone / detached 与 version.md
浏览器掐断识别为 WARN;上游中断时下发 detached;管理端 SSE 复用 AmcHttpSupport。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-12 17:40:00 +08:00
src/main release 1.0.1: SSE client-gone / detached 与 version.md 2026-10-12 17:40:00 +08:00
.gitignore Initial commit: AMC Spring Boot Starter 1.0.0 2026-10-12 09:32:22 +08:00
pom.xml release 1.0.1: SSE client-gone / detached 与 version.md 2026-10-12 17:40:00 +08:00
README.md release 1.0.1: SSE client-gone / detached 与 version.md 2026-10-12 17:40:00 +08:00
version.md release 1.0.1: SSE client-gone / detached 与 version.md 2026-10-12 17:40:00 +08:00

amcc-agent-chat-spring-boot-starter

对接 智能体管理中心(AMC,agent_manage_center_api) 的 Spring Boot Starter。
跑在宿主进程内:换票、缓存 chatToken、代理会话 / SSE / 转写 / 下载 / 智能体绑定,浏览器不直连 AMC,也不直连 Hermes TUI(方案 B)。

项 值
源码仓 https://forgejo.bingjun360.com/SouTan/amcc-agent-chat-spring-boot-starter
Maven 坐标 com.soutan:amcc-agent-chat-spring-boot-starter:1.0.1
Maven 仓库 https://forgejo.bingjun360.com/api/packages/SouTan/maven
包页 https://forgejo.bingjun360.com/SouTan/-/packages
Java 包名 com.mvker.amcc.*
运行要求 Spring Boot 2.7.x / Java 8
版本说明 见 version.md

1. 相对旧版 amcc 升级了什么

旧依赖是 amcc-agent-chat-client(无 Spring 的 HTTP 客户端)。宿主要自己写 Controller、token 缓存、用户解析、响应包装。

能力 旧 amcc-agent-chat-client 现 Starter 1.0.1
形态 纯 jar,无自动配置 Spring Boot Starter + spring.factories
Maven 坐标 com.souta:amcc-agent-chat-client(已废弃) com.soutan:amcc-agent-chat-spring-boot-starter
包仓库归属 文档曾写 adminUser SouTan 组织
HTTP 客户端 AmcAgentChatClient 需手建 Bean amc.enabled=true 时自动注册
chatToken 缓存 宿主自写 ConcurrentHashMap 内置 AmcAgentChatFacade(按 bizUserId)
管理端 HTTP 宿主手写 Controller 默认注册 /business/agentChat/*(可关)
大屏 HTTP 宿主手写 默认注册 /dashboard/agentChat/messages(可关)
智能体配置 宿主手写 默认注册 /system/agentConfig/*(可关)
用户 / 权限 / 响应 绑死宿主类型或复制代码 SPI:AmcBizUserResolver 等
路由注册方式 @RestController(易被 com.mvker 扫描重复) RouterFunction
legacy ws-handoff 常默认暴露 默认关闭(legacy-ws-handoff=false)

从旧 client 迁到 Starter 的步骤概要:

  1. 父 POM / 业务模块:去掉 amcc-agent-chat-client,改为 com.soutan:amcc-agent-chat-spring-boot-starter,仓库改为 …/api/packages/SouTan/maven。
  2. 删除宿主里自建的 AmcAgentChatClient @Bean、自写 token 缓存 Service(若已切到 Starter 路由,也可删对话/配置 Controller)。
  3. 配置 amc.*(见下文);实现至少一个 AmcBizUserResolver。
  4. 若前端 axios 要 code=200 的信封,实现 AmcJsonResponseWrapper;大屏实现 AmcDashboardUserResolver,并在安全配置里放行大屏路径(@AuthIgnore 扫不到 RouterFunction)。
  5. Reload Maven,回归管理端对话、下载、大屏发消息、配置 CRUD。

细节以能碳(yuanzhou_api)为准,见第 5 节。


2. Maven 引入

父 POM:

<properties>
  <amcc.starter.version>1.0.1</amcc.starter.version>
</properties>

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>com.soutan</groupId>
      <artifactId>amcc-agent-chat-spring-boot-starter</artifactId>
      <version>${amcc.starter.version}</version>
    </dependency>
  </dependencies>
</dependencyManagement>

<repositories>
  <repository>
    <id>forgejo</id>
    <url>https://forgejo.bingjun360.com/api/packages/SouTan/maven</url>
  </repository>
</repositories>

业务模块:

<dependency>
  <groupId>com.soutan</groupId>
  <artifactId>amcc-agent-chat-spring-boot-starter</artifactId>
</dependency>

~/.m2/settings.xml 中 server.id 必须是 forgejo,且开启 usePreemptive,否则拉私有包 / 发包会 401。


3. 配置项

amc:
  enabled: true
  base-url: http://127.0.0.1:9960          # AMC 基址,勿带尾斜杠
  tenant-code: your-tenant-code            # AMC tenant_info.tenant_code
  connect-timeout-ms: 5000
  read-timeout-ms: 30000
  long-read-timeout-ms: 120000
  token-refresh-skew-seconds: 120          # 可省略,视为 0
  # sse-read-timeout-ms: 320000
  bff:
    manage-enabled: true
    dashboard-enabled: true
    config-enabled: true
    legacy-ws-handoff: false
    # path-prefix: /business/agentChat
    # dashboard-path-prefix: /dashboard
    # config-path-prefix: /system/agentConfig

enabled=true 时,base-url / tenant-code / 三个超时必须显式填写,不会用 Java 默认值静默兜底。

开关 默认 作用
amc.bff.manage-enabled true 管理端对话路由
amc.bff.dashboard-enabled true 大屏发消息路由
amc.bff.config-enabled true 智能体配置路由
amc.bff.legacy-ws-handoff false 暴露 legacy ws-handoff

宿主若仍保留自己的同名 Controller,对应开关必须设为 false,否则路径冲突。


4. 自动注册的 HTTP

路由使用 RouterFunction,避免宿主 scanBasePackages 含 com.mvker 时再注册一份 Controller。

4.1 管理端(manage-enabled)

前缀默认 /business/agentChat,对齐 @souta/agent-chat-ui transport。

方法 路径 说明
GET /context 绑定智能体列表;{ superAdmin, agents[] }
GET /sessions?agentId= 会话列表
POST /sessions 创建 { agentId, title? }
POST /sessions/{storedId}/open 打开并拉历史
DELETE /sessions/{storedId}?agentId= 删除
POST /sessions/{sessionId}/messages SSE;JSON 或 multipart
POST /audio/transcribe 语音转写
GET /files/download?path= 附件字节流
POST /ws-handoff 仅 legacy-ws-handoff=true

当前用户来自 AmcBizUserResolver。chatToken 不出现在响应中。

4.2 大屏(dashboard-enabled)

方法 路径 说明
POST /dashboard/agentChat/messages SSE;body 含 unicode、text,可选 sessionId / agentId / 附件
POST /dashboard/hermes/ws-handoff 仅 legacy 打开时

unicode 交给 AmcDashboardUserResolver,不走登录用户,也不走对话权限门闩。

4.3 智能体配置(config-enabled)

方法 路径
POST /system/agentConfig/list
GET /system/agentConfig/profiles
GET /system/agentConfig/{id}
POST /system/agentConfig/add
POST /system/agentConfig/edit
POST /system/agentConfig/delete

列表字段:rows/total/current/size,行上可含姓名、部门等(由 AmcConfigUserDirectory 补齐)。

4.4 响应形态

  • JSON 成功体经 AmcJsonResponseWrapper(默认原样返回)。
  • SSE、文件下载不走包装。
  • 业务错误经 AmcExceptionTranslator(默认 AmcAgentChatAccessException)。

5. SPI

SPI 何时需要 作用
AmcBizUserResolver 开管理端 当前登录用户 → AMC bizUserId
AmcDashboardUserResolver 开大屏 场景键(如 unicode)→ 大屏业务用户 id
AmcConfigUserDirectory 配置页要展示/绑定本地用户 查 userName、昵称、部门等
AmcJsonResponseWrapper 前端要统一信封 如包成 { code, msg, data }
AmcExceptionTranslator 要统一异常类型 如转成宿主 BusinessException
AmcPermissionGate 要在 Starter 内做权限 checkConversationAccess / checkConfigAccess;默认放行

有 Spring Security 且 principal 为数字时,Starter 可提供默认 AmcBizUserResolver;LoginUser 一类必须自建 Bean。


6. 示范:能碳 yuanzhou_api 如何接入

能碳已完全依赖本 Starter:删除自研 AgentChatController / DashboardAgentChatController / AgentConfigController 及 BFF 实现,只保留适配代码。

6.1 依赖

父 POM nscode-business-base:

  • amcc.starter.version=1.0.1
  • dependencyManagement 管理 com.soutan:amcc-agent-chat-spring-boot-starter
  • repositories 增加 Forgejo SouTan Maven

业务模块 nscode-yuanzhou:

<dependency>
  <groupId>com.soutan</groupId>
  <artifactId>amcc-agent-chat-spring-boot-starter</artifactId>
</dependency>

6.2 yml(节选)

amc:
  enabled: true
  base-url: http://amc-prod.bingjun360.com/prod-api
  tenant-code: yuanzhouzhengfu
  connect-timeout-ms: 5000
  read-timeout-ms: 30000
  long-read-timeout-ms: 120000
  token-refresh-skew-seconds: 120
  # bff 开关未写 = 全部用默认 true;legacy-ws-handoff 默认 false

6.3 宿主适配类(保留在能碳源码)

类 路径 职责
YuanzhouAmcConfiguration com.mvker.amc.starter 五个 SPI Bean
YuanzhouAmcDashboardSecurity 同上 大屏路径 permitAll
DashboardSysUserSupport com.mvker.amc.support 查 user_name=dashboard 用户

AmcJsonResponseWrapper:包成 R.ok(body),满足 axios 拦截器对 code === 200 的要求。SSE / 下载不包装。

AmcBizUserResolver:SecurityUtils.getLoginUser(false).getUser().getUserId()。

AmcExceptionTranslator:转成 BusinessException,走 qlcloud 全局异常处理。

AmcDashboardUserResolver:DashboardUnicodeSupport.requireCommunity(unicode) 后,DashboardSysUserSupport.requireDashboardUserId()。

AmcConfigUserDirectory:SysUserMapper 批量/单条查询,填充 AmcConfigUser(含 userName,供绑定接口使用)。

YuanzhouAmcDashboardSecurity:@Order(1) 的 WebSecurityConfigurerAdapter,只匹配
{dashboardPathPrefix}/agentChat/** 与 …/hermes/** 并 permitAll。
原因:生产 debugMode=false 时,qlcloud 的 @AuthIgnore 只收集 @RequestMapping 的 Controller,扫不到 Starter 的 RouterFunction。

6.4 前端(不变路径)

管理端页 yuanzhou_ui/.../business/agentChat/index.vue:@souta/agent-chat-ui 的 AgentChatApp + transport 打 /business/agentChat/*(JWT 头 YzAuthorization)。

大屏 DashboardVoiceChat:transport 打 /dashboard/agentChat/messages,body 带页面闭包的 unicode。

6.5 能碳侧文件清单(接入后)

应存在:

  • nscode-business/.../amc/starter/YuanzhouAmcConfiguration.java
  • nscode-business/.../amc/starter/YuanzhouAmcDashboardSecurity.java
  • nscode-business/.../amc/support/DashboardSysUserSupport.java

不应再存在(已删除):

  • amc/controller/AgentChatController 等三套 Controller
  • amc/service/** BFF
  • 宿主自建的 amc/config/AmcAgentChatProperties(改用 Starter 的配置类)

7. 完整对接(新宿主)

按下列顺序做完,即可同时具备:管理端对话、大屏发消息、智能体配置 CRUD。
能碳落地细节见第 6 节;本节是可直接照抄改写的通用清单。

7.1 前置条件(AMC 侧)

  1. AMC 已部署,记下对外基址(如 http://amc-prod…/prod-api,勿带尾斜杠)。
  2. 在 AMC 建好租户,记下 tenant_code(对应宿主 amc.tenant-code)。
  3. 租户下已有可用 Hermes profile / 智能体定义;业务用户绑定可在宿主配置页完成,或先在 AMC 手工绑一条做联调。
  4. 宿主能访问 AMC 网络(防火墙 / 内网 DNS)。

7.2 Maven

父 POM(版本集中管理 + 仓库):

<properties>
  <amcc.starter.version>1.0.1</amcc.starter.version>
</properties>

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>com.soutan</groupId>
      <artifactId>amcc-agent-chat-spring-boot-starter</artifactId>
      <version>${amcc.starter.version}</version>
    </dependency>
  </dependencies>
</dependencyManagement>

<repositories>
  <repository>
    <id>forgejo</id>
    <url>https://forgejo.bingjun360.com/api/packages/SouTan/maven</url>
  </repository>
</repositories>

业务模块引入(不要再引旧的 amcc-agent-chat-client):

<dependency>
  <groupId>com.soutan</groupId>
  <artifactId>amcc-agent-chat-spring-boot-starter</artifactId>
</dependency>

本机 ~/.m2/settings.xml 中 server.id=forgejo,并开启 usePreemptive,否则拉包 401。

7.3 应用配置

amc:
  enabled: true
  base-url: http://amc-host:9960          # 换成真实 AMC
  tenant-code: your-tenant-code
  connect-timeout-ms: 5000
  read-timeout-ms: 30000
  long-read-timeout-ms: 120000
  token-refresh-skew-seconds: 120
  bff:
    manage-enabled: true                  # /business/agentChat/*
    dashboard-enabled: true               # /dashboard/agentChat/messages
    config-enabled: true                  # /system/agentConfig/*
    legacy-ws-handoff: false
    # 路径与前端不一致时再改;默认即可对接 @souta/agent-chat-ui
    # path-prefix: /business/agentChat
    # dashboard-path-prefix: /dashboard
    # config-path-prefix: /system/agentConfig

enabled=true 时 base-url、tenant-code、三个超时必须显式填写。
若宿主还留着同名 Controller,对应 *-enabled 先设 false,删掉旧代码后再打开。

7.4 宿主适配代码(建议单独包)

新建例如 com.example.amc.AmcHostConfiguration,在 amc.enabled=true 时注册 SPI。
下面是完整能力模板(管理端 + 大屏 + 配置页 + 统一 JSON/异常);按宿主类型替换注释处。

package com.example.amc;

import com.mvker.amcc.agentchat.AmcAgentChatClientException;
import com.mvker.amcc.spi.AmcBizUserResolver;
import com.mvker.amcc.spi.AmcConfigUser;
import com.mvker.amcc.spi.AmcConfigUserDirectory;
import com.mvker.amcc.spi.AmcDashboardUserResolver;
import com.mvker.amcc.spi.AmcExceptionTranslator;
import com.mvker.amcc.spi.AmcJsonResponseWrapper;
import com.mvker.amcc.spi.AmcPermissionGate;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Collection;
import java.util.Collections;
import java.util.Map;

@Configuration
@ConditionalOnProperty(prefix = "amc", name = "enabled", havingValue = "true")
public class AmcHostConfiguration {

  /** 管理端:当前登录用户 → AMC bizUserId。LoginUser 等包装类型必须自建,不能依赖默认数字 principal。 */
  @Bean
  @ConditionalOnMissingBean
  public AmcBizUserResolver amcBizUserResolver() {
    return () -> {
      // return SecurityUtils.getLoginUser(...).getUser().getUserId();
      return null;
    };
  }

  /**
   * 前端若要求 { code, msg, data } 一类信封,在此包装。
   * 默认不包装;SSE / 文件下载不会调用本方法。
   */
  @Bean
  @ConditionalOnMissingBean
  public AmcJsonResponseWrapper amcJsonResponseWrapper() {
    return body -> {
      // 例:return body == null ? R.ok() : R.ok(body);
      return body;
    };
  }

  /** 把 Starter / AMC 错误转成宿主全局异常处理能识别的类型。 */
  @Bean
  @ConditionalOnMissingBean
  public AmcExceptionTranslator amcExceptionTranslator() {
    return new AmcExceptionTranslator() {
      @Override
      public RuntimeException translate(String message) {
        return new IllegalStateException(message); // 换成 BusinessException 等
      }

      @Override
      public RuntimeException translate(AmcAgentChatClientException exception) {
        return new IllegalStateException(exception.getMessage());
      }
    };
  }

  /**
   * 大屏:请求体 sceneKey(能碳为 unicode)→ 固定大屏业务用户 id。
   * 不开大屏时可删本 Bean,并设 amc.bff.dashboard-enabled=false。
   */
  @Bean
  @ConditionalOnMissingBean
  public AmcDashboardUserResolver amcDashboardUserResolver() {
    return sceneKey -> {
      // 1) 校验 sceneKey 合法(社区/门店等)
      // 2) 返回大屏专用用户主键(AMC 侧按此 bizUserId 换票、绑智能体)
      throw new IllegalStateException("请实现大屏用户解析: " + sceneKey);
    };
  }

  /**
   * 配置页:按 bizUserId 补 userName / 昵称 / 部门等;add/edit 绑定时必须能提供 userName。
   * 不开配置页时可删,并设 amc.bff.config-enabled=false。
   */
  @Bean
  @ConditionalOnMissingBean
  public AmcConfigUserDirectory amcConfigUserDirectory() {
    return new AmcConfigUserDirectory() {
      @Override
      public AmcConfigUser findById(Long userId) {
        if (userId == null) {
          return null;
        }
        // SysUser u = userMapper.selectById(userId);
        // return AmcConfigUser.builder().userId(...).userName(...).nickName(...).build();
        return null;
      }

      @Override
      public Map<Long, AmcConfigUser> findByIds(Collection<Long> userIds) {
        if (userIds == null || userIds.isEmpty()) {
          return Collections.emptyMap();
        }
        // 批量查询后 toMap(AmcConfigUser::getUserId, ...)
        return Collections.emptyMap();
      }
    };
  }

  /**
   * 可选。默认全部放行。
   * checkConversationAccess:管理端对话;checkConfigAccess:配置 CRUD(action=list/query/add/edit/remove)。
   * 抛出的异常不会再经 AmcExceptionTranslator。大屏不走本门闩。
   */
  @Bean
  @ConditionalOnMissingBean
  public AmcPermissionGate amcPermissionGate() {
    return new AmcPermissionGate() {
      @Override
      public void checkConversationAccess() {
        // 例:SecurityUtils.checkPermi("business:agentChat:list");
      }

      @Override
      public void checkConfigAccess(String action) {
        // 例:按 action 映射 system:agentConfig:*
      }
    };
  }
}

确保该类被宿主组件扫描到(与启动类同根包,或 @Import / @SpringBootApplication(scanBasePackages=…))。

7.5 安全放行(大屏必做)

Starter 用 RouterFunction 注册路由。若宿主依赖「注解扫 Controller 生成匿名名单」(如 @AuthIgnore),扫不到这些路径。

大屏一般无登录 JWT,必须单独 permitAll,例如(Spring Security 5 / Boot 2.7):

@Configuration
@Order(1)
@ConditionalOnProperty(prefix = "amc", name = "enabled", havingValue = "true")
public class AmcDashboardSecurity extends WebSecurityConfigurerAdapter {

  private final AmcAgentChatProperties properties;

  public AmcDashboardSecurity(AmcAgentChatProperties properties) {
    this.properties = properties;
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    String prefix = properties.getBff() != null && StringUtils.hasText(properties.getBff().getDashboardPathPrefix())
        ? properties.getBff().getDashboardPathPrefix().trim()
        : "/dashboard";
    if (!prefix.startsWith("/")) {
      prefix = "/" + prefix;
    }
    while (prefix.endsWith("/")) {
      prefix = prefix.substring(0, prefix.length() - 1);
    }
    http.requestMatchers()
        .antMatchers(prefix + "/agentChat/**", prefix + "/hermes/**")
        .and()
        .csrf().disable()
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()
        .authorizeRequests()
        .anyRequest().permitAll();
  }
}

管理端 /business/agentChat/**、配置 /system/agentConfig/** 继续走宿主登录与菜单权限即可。

7.6 前端对接

场景 包 / 页面 请求目标(默认前缀) 鉴权
管理端对话 @souta/agent-chat-ui 的 AgentChatApp + transport 宿主 /business/agentChat/* 宿主 JWT(如能碳 YzAuthorization)
大屏 自建 VoiceChat / 同 UI 包 POST /dashboard/agentChat/messages 通常无 JWT;body 带 unicode(或约定的 sceneKey)、text
智能体配置 管理端配置页 /system/agentConfig/* 宿主 JWT + 菜单权限

约定:

  • 浏览器只打宿主,不打 AMC,也不带 chatToken。
  • JSON 成功体若走了 AmcJsonResponseWrapper,拦截器按宿主约定认 code(能碳为 200)。
  • SSE、附件下载按二进制 / event-stream 处理,不要当 JSON 信封解析。

7.7 数据与权限约定

概念 谁负责
bizUserId 宿主用户主键;AMC 按租户 + 该 id 换 chatToken、查绑定
大屏用户 建议固定系统用户(如 user_name=dashboard),由 AmcDashboardUserResolver 返回其 id;智能体绑在该用户上
配置页展示字段 AmcConfigUserDirectory 从本地用户表补齐;userName 写入 AMC 绑定
菜单权限 默认不在 Starter 内校验;需要时实现 AmcPermissionGate

7.8 联调验收清单

  1. 依赖:Reload Maven,启动日志无 amc.* 缺配置报错。
  2. 管理端:登录后 GET /business/agentChat/context 返回智能体列表;能建会话、SSE 回消息、附件下载(含路径含 + 等字符)。
  3. 大屏:无登录调用 POST /dashboard/agentChat/messages,带合法 sceneKey,SSE 有回复;非法 sceneKey 被宿主校验拒绝。
  4. 配置:POST /system/agentConfig/list 有分页;add/edit 后管理端 context 能看到新绑定(必要时重新打开页以触发换票)。
  5. 负例:未登录打管理端应 401/业务未登录;关闭某 bff.*-enabled 后对应路径 404。
  6. 不要在浏览器 Network 里看到 X-Agent-Chat-Token 或 AMC 域名(应只出现在宿主→AMC)。

7.9 常见坑

现象 处理
拉包 401 settings.xml 的 forgejo + usePreemptive
启动失败缺配置 enabled=true 时补齐 base-url / tenant-code / 超时
路径 404 或双注册 删旧 Controller,或关掉对应 amc.bff.*-enabled
前端一直无数据 / code 不对 实现 AmcJsonResponseWrapper
大屏 401 按 7.5 对 RouterFunction 路径 permitAll
绑了智能体 context 仍空 增绑后需重新换票;Starter 在 contextAgents 场景会强制刷新
配置 add 缺 userName AmcConfigUserDirectory.findById 必须填 userName

裁剪能力时:只用管理端 → 可省 Dashboard / Config SPI,并设 dashboard-enabled、config-enabled 为 false。完整三端对接以本节 7.1–7.8 为准;对照实现见第 6 节能碳。


8. 与 AMC / 前端的关系

浏览器 (@souta/agent-chat-ui)
    │  宿主 JWT
    ▼
宿主应用 + 本 Starter(换票、缓存 chatToken、BFF HTTP)
    │  X-Agent-Chat-Token
    ▼
AMC agent_manage_center_api  (/open/agentChat、/open/agentConfig)
    │
    ▼
Hermes Dashboard
  • 本仓库:宿主侧依赖(源码 + Maven 包)。
  • agent_manage_center_api:AMC 服务端;README 里有对接摘要,完整用法以本文为准。
  • 前端包 @souta/agent-chat-ui:npm 作用域名,与 Forgejo 组织名 SouTan、Maven groupId com.soutan 不是同一概念。

9. 发布新版本

mvn -q clean package source:jar -DskipTests
mvn deploy:deploy-file -DgroupId=com.soutan -DartifactId=amcc-agent-chat-spring-boot-starter -Dversion=x.y.z -Dpackaging=jar -Dfile=target/amcc-agent-chat-spring-boot-starter-x.y.z.jar -Dsources=target/amcc-agent-chat-spring-boot-starter-x.y.z-sources.jar -DgeneratePom=true -DrepositoryId=forgejo -Durl=https://forgejo.bingjun360.com/api/packages/SouTan/maven

同一版本不能重复发;覆盖前先在包页删除旧版本。发版后更新 version.md,并 bump 宿主的 amcc.starter.version。