Go: add soft fingerprint framework (#17837)

Signed-off-by: Jin Hai <haijin.chn@gmail.com>
This commit is contained in:
Jin Hai
2026-08-05 13:54:09 +08:00
committed by GitHub
parent f057544e31
commit 1e78789448
8 changed files with 191 additions and 0 deletions

View File

@@ -823,6 +823,47 @@ func (h *Handler) UpdateSystemLicenseConfig(c *gin.Context) {
common.SuccessWithData(c, result, "System license config updated successfully")
}
type SetSoftFingerprintRequest struct {
SoftFingerprint string `json:"fingerprint" binding:"required"`
}
func (h *Handler) SetSoftFingerprint(c *gin.Context) {
var req SetSoftFingerprintRequest
if err := c.ShouldBindJSON(&req); err != nil {
println("JSON bind error: %v (type: %T)", err, err)
common.ErrorWithCode(c, common.CodeBadRequest, err.Error())
return
}
err := h.service.SetSoftFingerprint(req.SoftFingerprint)
if err != nil {
common.ErrorWithCode(c, common.CodeServerError, err.Error())
return
}
common.SuccessNoData(c, "SUCCESS")
}
func (h *Handler) ShowSoftFingerprint(c *gin.Context) {
softFingerprint, err := h.service.GetSoftFingerprint()
if err != nil {
common.ErrorWithCode(c, common.CodeServerError, err.Error())
return
}
common.SuccessWithData(c, softFingerprint, "")
}
func (h *Handler) DeleteSoftFingerprint(c *gin.Context) {
err := h.service.DeleteSoftFingerprint()
if err != nil {
common.ErrorWithCode(c, common.CodeServerError, err.Error())
return
}
common.SuccessNoData(c, "SUCCESS")
}
type ShowUserActivityRequest struct {
Days int `json:"days"`
Email string `json:"email"`

View File

@@ -70,6 +70,10 @@ func RegisterEERouter(protected *gin.RouterGroup, r *Router) {
protected.GET("/system/license", r.handler.ShowSystemLicense)
protected.PUT("/system/license/config", r.handler.UpdateSystemLicenseConfig)
protected.POST("/system/soft-fingerprint", r.handler.SetSoftFingerprint)
protected.GET("/system/soft-fingerprint", r.handler.ShowSoftFingerprint)
protected.DELETE("/system/soft-fingerprint", r.handler.DeleteSoftFingerprint)
protected.GET("/fingerprint", r.handler.GetFingerprint)
protected.POST("/license", r.handler.SetLicense)
protected.POST("/license/config", r.handler.UpdateLicenseConfig)

View File

@@ -402,6 +402,23 @@ func (s *Service) UpdateSystemLicenseConfig(timeRecordSaveInterval, timeRecordTa
return result, nil
}
func (s *Service) SetSoftFingerprint(softFingerprint string) error {
return errors.New("set soft fingerprint is not supported")
}
func (s *Service) GetSoftFingerprint() (map[string]interface{}, error) {
result := map[string]interface{}{
"command": "show_soft_fingerprint",
"error": "'show soft fingerprint' is not supported",
}
return result, nil
}
func (s *Service) DeleteSoftFingerprint() error {
return errors.New("delete soft fingerprint is not supported")
}
// ShowUserActivity show user activity for enterprise edition
func (s *Service) ShowUserActivity(email string, days int) (map[string]interface{}, error) {
// Query user by email

View File

@@ -808,6 +808,27 @@ func (c *CLI) AdminSetLicenseCommand(cmd *Command) (ResponseIf, error) {
return HandleCommonDataResponse(resp, "set license")
}
func (c *CLI) AdminSetSoftFingerprintCommand(cmd *Command) (ResponseIf, error) {
if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil {
return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login")
}
softFingerprint, ok := cmd.Params["soft_fingerprint"].(string)
if !ok {
return nil, fmt.Errorf("soft_fingerprint not provided")
}
payload := map[string]interface{}{
"fingerprint": softFingerprint,
}
resp, err := c.AdminServerClient.Request("POST", "/admin/system/soft-fingerprint", "admin", nil, payload)
if err != nil {
return nil, fmt.Errorf("failed to set soft fingerprint: %w", err)
}
return HandleSimpleResponse(resp, "set soft fingerprint")
}
func (c *CLI) AdminSetLicenseConfigCommand(cmd *Command) (ResponseIf, error) {
if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil {
return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login")
@@ -1348,6 +1369,22 @@ func (c *CLI) AdminShowFingerprintCommand(cmd *Command) (ResponseIf, error) {
return HandleCommonDataResponse(resp, "show fingerprint")
}
// AdminShowSoftFingerprintCommand show soft fingerprint command (admin mode only)
func (c *CLI) AdminShowSoftFingerprintCommand(cmd *Command) (ResponseIf, error) {
if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil {
return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login")
}
apiURL := fmt.Sprintf("/admin/system/soft-fingerprint")
resp, err := c.AdminServerClient.Request("GET", apiURL, "admin", nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to show soft fingerprint: %w", err)
}
return HandleCommonDataResponse(resp, "show soft fingerprint")
}
// AdminShowLicenseCommand show license command (admin mode only)
func (c *CLI) AdminShowLicenseCommand(cmd *Command) (ResponseIf, error) {
if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil {
@@ -2826,6 +2863,23 @@ func (c *CLI) AdminDeleteModelsCommand(cmd *Command) (ResponseIf, error) {
return HandleCommonDataResponse(resp, fmt.Sprintf("remove model %s", modelNames))
}
// AdminDeleteSoftFingerprintCommand delete soft fingerprint
func (c *CLI) AdminDeleteSoftFingerprintCommand(cmd *Command) (ResponseIf, error) {
if c.Config.CLIMode != AdminMode || c.AdminServerClient.LoginToken == nil {
return nil, fmt.Errorf("this command is only allowed in ADMIN mode or already login")
}
apiURL := fmt.Sprintf("/admin/system/soft-fingerprint")
resp, err := c.AdminServerClient.Request("DELETE", apiURL, "admin", nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to remove soft fingerprint: %w", err)
}
return HandleSimpleResponse(resp, fmt.Sprintf("remove soft fingerprint"))
}
func (c *CLI) AdminShowLogLevelCommand(cmd *Command) (ResponseIf, error) {
if c.Config.CLIMode != AdminMode {

View File

@@ -408,6 +408,8 @@ func (p *Parser) parseAdminShowCommands() (*Command, error) {
return p.parseAdminShowCurrent()
case TokenFingerprint:
return p.parseAdminShowFingerprint()
case TokenSoft:
return p.parseAdminShowSoftFingerprint()
case TokenLicense:
return p.parseAdminShowLicense()
case TokenProvider:
@@ -736,6 +738,23 @@ func (p *Parser) parseAdminShowFingerprint() (*Command, error) {
return NewCommand("admin_show_fingerprint"), nil
}
// SHOW SOFT FINGERPRINT;
func (p *Parser) parseAdminShowSoftFingerprint() (*Command, error) {
p.nextToken() // consume SOFT
if p.curToken.Type != TokenFingerprint {
return nil, fmt.Errorf("expected FINGERPRINT")
}
p.nextToken()
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
return NewCommand("admin_show_soft_fingerprint"), nil
}
// SHOW LICENSE;
func (p *Parser) parseAdminShowLicense() (*Command, error) {
p.nextToken() // consume LICENSE
@@ -1836,6 +1855,8 @@ func (p *Parser) parseAdminSetCommand() (*Command, error) {
switch p.curToken.Type {
case TokenLicense:
return p.parseAdminSetLicense()
case TokenSoft:
return p.parseAdminSetSoftFingerprint()
case TokenVar:
return p.parseAdminSetVariable()
case TokenRole:
@@ -1885,6 +1906,30 @@ func (p *Parser) parseAdminSetLicense() (*Command, error) {
return cmd, nil
}
func (p *Parser) parseAdminSetSoftFingerprint() (*Command, error) {
p.nextToken() // consume SOFT
if p.curToken.Type != TokenFingerprint {
return nil, fmt.Errorf("expected FINGERPRINT")
}
p.nextToken()
softFingerprint, err := p.parseQuotedString()
if err != nil {
return nil, err
}
p.nextToken()
cmd := NewCommand("admin_set_soft_fingerprint")
cmd.Params["soft_fingerprint"] = softFingerprint
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
return cmd, nil
}
func (p *Parser) parseAdminSetVariable() (*Command, error) {
p.nextToken() // consume VAR
@@ -2280,6 +2325,8 @@ func (p *Parser) parseAdminDeleteCommands() (*Command, error) {
return p.parseAPIDeleteAdminServer()
case TokenProvider:
return p.parseAdminDeleteProvider()
case TokenSoft:
return p.parseAdminDeleteSoftFingerprint()
default:
return nil, fmt.Errorf("unknown ADD target: %s", p.curToken.Value)
}
@@ -2359,6 +2406,25 @@ func (p *Parser) parseAdminDeleteModel(providerName, instanceName string) (*Comm
return cmd, nil
}
// syntax: delete soft fingerprint;
func (p *Parser) parseAdminDeleteSoftFingerprint() (*Command, error) {
p.nextToken() // consume SOFT
if p.curToken.Type != TokenFingerprint {
return nil, fmt.Errorf("expected FINGERPRINT after SOFT")
}
p.nextToken()
cmd := NewCommand("admin_delete_soft_fingerprint")
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
return cmd, nil
}
func (p *Parser) parseAdminEnableCommand() (*Command, error) {
p.nextToken() // consume ENABLE
switch p.curToken.Type {

View File

@@ -111,6 +111,8 @@ func (c *CLI) ExecuteAdminCommand(cmd *Command) (ResponseIf, error) {
return c.AdminShowVariable(cmd)
case "admin_set_license":
return c.AdminSetLicenseCommand(cmd)
case "admin_set_soft_fingerprint":
return c.AdminSetSoftFingerprintCommand(cmd)
case "admin_set_license_config":
return c.AdminSetLicenseConfigCommand(cmd)
case "admin_set_variable":
@@ -177,6 +179,8 @@ func (c *CLI) ExecuteAdminCommand(cmd *Command) (ResponseIf, error) {
return c.CommonCheckProviderConnectionCommand(cmd)
case "admin_show_fingerprint":
return c.AdminShowFingerprintCommand(cmd)
case "admin_show_soft_fingerprint":
return c.AdminShowSoftFingerprintCommand(cmd)
case "admin_show_license":
return c.AdminShowLicenseCommand(cmd)
case "admin_show_user":
@@ -277,6 +281,8 @@ func (c *CLI) ExecuteAdminCommand(cmd *Command) (ResponseIf, error) {
return c.AdminDeleteInstancesCommand(cmd)
case "admin_delete_model":
return c.AdminDeleteModelsCommand(cmd)
case "admin_delete_soft_fingerprint":
return c.AdminDeleteSoftFingerprintCommand(cmd)
case "admin_enable_model":
return c.CommonEnableOrDisableModelCommand(cmd, "enable")
case "admin_disable_model":

View File

@@ -377,6 +377,8 @@ func (l *Lexer) lookupIdent(ident string) Token {
return Token{Type: TokenRetrieve, Value: ident}
case "CURRENT":
return Token{Type: TokenCurrent, Value: ident}
case "SOFT":
return Token{Type: TokenSoft, Value: ident}
case "FINGERPRINT":
return Token{Type: TokenFingerprint, Value: ident}
case "LICENSE":

View File

@@ -108,6 +108,7 @@ const (
TokenMemory
TokenRetrieve
TokenCurrent
TokenSoft
TokenFingerprint
TokenLicense
TokenVision