Files
ryan 6f5aaa9f85 fix(价格展示): 修复模板渲染时极小价格被折叠为 0.00 的问题
Motivation:
此前的修复仅覆盖直接拼接文案的场景,跟单等基于模板渲染的推送场景中,%.2f 格式化仍会把 PEPE 等 Meme 币的极小价格(如 0.00000059)折叠为 0.00,导致交易通知丢失真实价格、误导用户。

Changes:

* 抽取共享的价格显示逻辑到统一显示层,两个订阅者改为复用,移除重复实现
* 新增 Decimal 类型,渲染时以副本方式包装模板数据,使模板内 printf 风格格式化保留极小价格精度且不出现科学计数法,同时不修改调用方原始数据
* 渲染引擎空值判断改用反射实现,覆盖全部整型、无符号整型与浮点类型
* 补充转换到渲染的端到端回归测试,覆盖极小价格精度、科学计数法与数据不可变性
2026-08-23 00:12:41 +08:00

102 lines
2.2 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package engine
import (
"bytes"
"fmt"
"reflect"
"strings"
"text/template"
"aiaa-notification-service/internal/display"
)
type Renderer struct{}
func NewRenderer() *Renderer {
return &Renderer{}
}
func (r *Renderer) Render(tmplContent string, data map[string]interface{}) (string, error) {
tmpl, err := template.New("notify").
Option("missingkey=zero").
Funcs(template.FuncMap{
"line": templateLine,
"case": templateCase,
"replace": strings.ReplaceAll,
}).
Parse(tmplContent)
if err != nil {
return "", fmt.Errorf("parse template: %w", err)
}
var buf bytes.Buffer
if err := tmpl.Execute(&buf, display.WrapMap(data)); err != nil {
return "", fmt.Errorf("execute template: %w", err)
}
return buf.String(), nil
}
// templateLine renders "labelvalue\n" or empty if value is missing/zero.
func templateLine(label string, v any) string {
if isEmptyValue(v) {
return ""
}
return strings.TrimRight(label, ":") + "" + fmt.Sprint(v) + "\n"
}
func templateCase(value any, pairs ...any) string {
got := stringify(value)
n := len(pairs)
def := got
if n%2 == 1 {
def = stringify(pairs[n-1])
pairs = pairs[:n-1]
}
for i := 0; i+1 < len(pairs); i += 2 {
if caseKeyMatch(got, stringify(pairs[i])) {
return stringify(pairs[i+1])
}
}
return def
}
func caseKeyMatch(value, key string) bool {
v := strings.ToLower(strings.TrimSpace(value))
k := strings.ToLower(strings.TrimSpace(key))
k = strings.TrimPrefix(k, ".")
vSeg := v
if i := strings.LastIndex(v, "."); i >= 0 {
vSeg = v[i+1:]
}
return k == v || k == strings.TrimPrefix(v, ".") || k == vSeg
}
func stringify(v any) string {
if v == nil {
return ""
}
return fmt.Sprint(v)
}
func isEmptyValue(v any) bool {
switch t := v.(type) {
case nil:
return true
case string:
return strings.TrimSpace(t) == ""
case bool:
return !t
default:
rv := reflect.ValueOf(v)
switch rv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return rv.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return rv.Uint() == 0
case reflect.Float32, reflect.Float64:
return rv.Float() == 0
default:
return false
}
}
}