07955ddf07
服务号「植趣 ZeeQ」已认证,走网页授权(snsapi_base,只拿 openid、用户无感), 不接管消息推送,副作用最小。 流程:PC 建 ticket → 二维码指向 /wx/mp?t= → 用户微信扫码 → 302 到微信授权页 → 回调 /api/v1/wx/mp/callback 用 code 换 openid → 找/建用户 → ticket 置 authorized → PC 轮询 /wx/mp/poll 拿到 authorized → 签发 JWT。ticket 一次性消费防重放。 - 配置(appid/secret/base_url)后台可改,secret AES 加密入库,与微信支付同一套 secrets; - ticket 存 Redis(短 TTL),无 Redis 时回退进程内内存(本地单实例可用,生产必须有 Redis); - User 加 wechat_openid。**部分唯一索引**(WHERE openid <> '')而非普通唯一: 存量邮箱用户该列是空串,普通唯一索引会让多个空串互撞、AutoMigrate 直接失败 —— 与之前 NULL 余额同类的坑,这次提前避开。 单测覆盖:授权 URL 拼接(含 #wechat_redirect 锚点必须在末尾)、secret 加密往返、 建号/查号、空 openid 不误命中存量用户。微信 API 调用依赖公网回调,本地测不了, 留待部署后真机扫码。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
98 lines
3.8 KiB
Go
98 lines
3.8 KiB
Go
package store
|
|
|
|
import "context"
|
|
|
|
// 评测观测查询(admin「自动评测」页做实用;此前该页纯 mock)。全平台口径 → WithoutTenant。
|
|
// 注:评测的「纠偏前后全文轨迹」后端未持久化,只存了 Reason(评语)/Corrected(是否已纠偏采纳)/
|
|
// 各维度分,故错题本展示这些真数据,不含编造的 before/after 对照。
|
|
|
|
// EvalDay 是评测趋势按天一行。
|
|
type EvalDay struct {
|
|
Day string `json:"day"` // YYYYMMDD
|
|
AvgOverall float64 `json:"avg_overall"` // 当日综合分均值 [0,1]
|
|
AvgFaithful float64 `json:"avg_faithful"` // 当日忠实度均值(仅计有来源的评测)
|
|
Count int64 `json:"count"`
|
|
PoorCount int64 `json:"poor_count"` // 当日 poor 级条数(幻觉/低质趋势)
|
|
}
|
|
|
|
// EvalTrend 按天聚合评测(from/to 为 YYYYMMDD)。avg_faithful 只算有检索来源的评测(sources>0),
|
|
// 无来源的忠实度恒 0 会把均值压低失真。
|
|
func (p *Postgres) EvalTrend(ctx context.Context, from, to string) []EvalDay {
|
|
if p.db == nil {
|
|
return nil
|
|
}
|
|
var out []EvalDay
|
|
p.db.WithContext(ctx).Model(&Eval{}).
|
|
Select("to_char(created_at,'YYYYMMDD') as day, "+
|
|
"avg(overall) as avg_overall, "+
|
|
"avg(case when sources > 0 then faithful end) as avg_faithful, "+
|
|
"count(*) as count, "+
|
|
"count(case when level = 'poor' then 1 end) as poor_count").
|
|
Where("to_char(created_at,'YYYYMMDD') >= ? AND to_char(created_at,'YYYYMMDD') <= ?", from, to).
|
|
Group("day").Order("day").Scan(&out)
|
|
return out
|
|
}
|
|
|
|
// EvalSummary 是评测总览计数。
|
|
type EvalSummary struct {
|
|
Total int64 `json:"total"`
|
|
OK int64 `json:"ok"`
|
|
Warn int64 `json:"warn"`
|
|
Poor int64 `json:"poor"`
|
|
Corrected int64 `json:"corrected"` // 经低分自动纠偏重生成后采纳的条数(恒温器闭环成效)
|
|
AvgOverall float64 `json:"avg_overall"` // 区间综合分均值
|
|
}
|
|
|
|
// EvalSummaryFor 区间内评测计数(from/to 为 YYYYMMDD)。
|
|
func (p *Postgres) EvalSummaryFor(ctx context.Context, from, to string) EvalSummary {
|
|
var s EvalSummary
|
|
if p.db == nil {
|
|
return s
|
|
}
|
|
p.db.WithContext(ctx).Model(&Eval{}).
|
|
Select("count(*) as total, "+
|
|
"count(case when level='ok' then 1 end) as ok, "+
|
|
"count(case when level='warn' then 1 end) as warn, "+
|
|
"count(case when level='poor' then 1 end) as poor, "+
|
|
"count(case when corrected then 1 end) as corrected, "+
|
|
"coalesce(avg(overall),0) as avg_overall").
|
|
Where("to_char(created_at,'YYYYMMDD') >= ? AND to_char(created_at,'YYYYMMDD') <= ?", from, to).
|
|
Scan(&s)
|
|
return s
|
|
}
|
|
|
|
// PoorEval 是错题本一行(低分评测 + 评语 + 纠偏标记;带租户名免前端二次查)。
|
|
type PoorEval struct {
|
|
TaskID string `json:"task_id"`
|
|
TenantName string `json:"tenant_name"`
|
|
Owner string `json:"owner"`
|
|
Overall float64 `json:"overall"`
|
|
Rule float64 `json:"rule"`
|
|
LLM float64 `json:"llm"`
|
|
Faithful float64 `json:"faithful"`
|
|
Level string `json:"level"`
|
|
Reason string `json:"reason"`
|
|
Sources int `json:"sources"`
|
|
Corrected bool `json:"corrected"`
|
|
CreatedAt string `json:"created_at"`
|
|
}
|
|
|
|
// PoorEvals 最近的低分评测(level=poor/warn,错题本)。
|
|
func (p *Postgres) PoorEvals(ctx context.Context, limit int) []PoorEval {
|
|
if p.db == nil {
|
|
return nil
|
|
}
|
|
if limit <= 0 || limit > 100 {
|
|
limit = 30
|
|
}
|
|
var out []PoorEval
|
|
p.db.WithContext(ctx).Table("sundynix_eval e").
|
|
Select("e.task_id, coalesce(t.name,'') as tenant_name, e.owner, e.overall, e.rule, e.llm, " +
|
|
"e.faithful, e.level, e.reason, e.sources, e.corrected, " +
|
|
"to_char(e.created_at,'YYYY-MM-DD HH24:MI') as created_at").
|
|
Joins("left join sundynix_tenant t on t.id = e.tenant_id").
|
|
Where("e.level in ('poor','warn') AND e.deleted_at IS NULL").
|
|
Order("e.created_at desc").Limit(limit).Scan(&out)
|
|
return out
|
|
}
|