微信小程序-评价内容安全解决方案
1、需求提出
业务:“订单完成后需要增加一个用户评价的功能”。
产品:“好的,我们在前端新做一个页面可以评价。有好评、一般、差评,再加一个文本框输入评价内容。”
开发:“可以做。”
业务&产品:“多久能完成?我们希望尽快上线,如果可以的话,想自动生成一些评价的数据,在详细页显示 ♪(^∇^*)。这样对增加业务量有很大帮助。”
开发:“有啥问题呢,产品给方案给设计就能做啊。时间还得看设计方案WBS之后才好确认。”
业务:“一个星期够了吧,这个着急上线。就一个简单的评价功能。”
产品:“业务需求确认了,我去找UI设计下。过2天给你原型。”
Two days later...
产品@开发:“具体需求(附带一个链接)。”
打开链接一看,确实很简单:
1、一个前端的评价页面:上面是评价等级,五个小星星。下面一个评价内容文本框。提交还弹窗“评价成功! 产品详情查看评价内容”。
2、一个管理后台页面(没有UI设计,比较粗糙):类似列表显示评价内容,上面搜索条件,列表字段:订单号、用户、评价等级、内容、时间。尼玛还有一个导出功能,这是从其他页面截图的吧,这评价还导出??
开发心里OS:“这虽然有点粗鄙,但也要先做吧,毕竟后面做出来之后他们就会基于这个完善(修改)了😁。先设计表结构、再出接口、最后管理后台页面,这样就能前后端并行节省时间,提高效率。”
前端@产品:“这只有选中的⭐样式图片,不选中的是啥样?能半颗星选吗?评价多少个字啊,测试会测一个字的🙄。” (前端还是挺负责的搞事情 😖)
开发设计:
1、评价关联订单,订单中可能多个产品,评价也要对应产品吧,但是这需求没体现啊,咋搞,唉。还得设计兼容。
2、评价字段只有评价等级、评价内容。如果以后增加服务评价、物流评价呢,现在要不要考虑扩展。难搞,一周上线来得及不,还得讨论。
3、用户评价,看起来是评价,但是是用户自主输入,万一有啥不健康内容😏,或评价负面情绪,这对业务或平台都是伤害啊。
4、用户追评、添加图片、视频。。。存储问题。。。
这时突然响起:李玖哲的《想太多》 :“是我想太多你总这样说,但你却没有真的心疼我。是我想太多我也这样说,这是唯一能安慰我的理由”。
打住,打住。别往下想了。需求已经来了,时间已经定了,总要先开发,拿点东西出来吧。
OK,OK。难不倒聪明的程序员。MVP最小可行产品先出来,后面在迭代更新。
One week later。。。
基本功能的评价(评价等级、内容)功能已上线。
一波推广和宣传后,陆续产生了很多评价。各种各样的,好评、吐槽都有。随意评价也有。这些评价都显示在了前端,突然业务紧急在群里@开发,有一个恶意评价需要处理。
呵呵😄,早就料到会有这种情况。不慌,开发时已经预留了逻辑删除,在后台开放此功能即可。
新的问题产生:不能让人工一个个审核吧。
答案是肯定的,给钱就行。
寻找解决方案,思路:
1、在输入时控制,检测内容安全信息。
2、在后台可过滤显示内容,限制显示或隐藏评论。
3、加入AI识别评论情绪,对好评不限制,对消极的评价做标识,不显示到用户前端。
微信小程序有“文本内容安全检测”接口,检查一段文本是否含有违法违规内容。
应用场景举例:
- 用户个人资料违规文字检测;
- 媒体新闻类用户发表文章,评论内容检测;
- 游戏类用户编辑上传的素材(如答题类小游戏用户上传的问题及答案)检测等。 频率限制:单个 appId 调用上限为 4000 次/分钟,2,000,000 次/天。
直接在用户输入完成后通过 HTTPS 调用,如果存在违规内容提示用户健康评论。
 
1 public static MsgSecCheck_resp MsgSecCheck(MsgSecCheck_req model) 2 #region 3 { 4 string ACCESS_TOKEN = GetAccessToken(); 5 string url = $"https://api.weixin.qq.com/wxa/msg_sec_check?access_token={ACCESS_TOKEN}"; 6 try 7 { 8 var json = HttpClientUti.DoPost(url, JsonHelper.ObjectToJson(model)); 9 var remodel = JsonHelper.JsonToObject<MsgSecCheck_resp>(json); 10 return remodel; 11 } 12 catch (Exception ex) 13 { 14 return new MsgSecCheck_resp { errcode = -1, errmsg = "msgSecCheck接口异常," + ex.Message }; 15 } 16 } 17 #endregion
 
1 #region models 2 public class MsgSecCheck_req 3 #region 4 { 5 /// <summary> 6 /// 用户的openid(用户需在近两小时访问过小程序) 7 /// </summary> 8 public string openid { get; set; } 9 10 /// <summary> 11 /// 场景枚举值(1 资料;2 评论;3 论坛;4 社交日志) 12 /// </summary> 13 public int scene { get; set; } 14 15 /// <summary> 16 /// 接口版本号,2.0版本为固定值2 17 /// </summary> 18 public int version { get; set; } = 2; 19 public string content { get; set; } 20 } 21 #endregion 22 public class MsgSecCheck_resp 23 #region 24 { 25 public int errcode { get; set; } 26 public string errmsg { get; set; } 27 public Result_Entity result { get; set; } 28 public List<Detail_Entity> detail { get; set; } 29 public string trace_id { get; set; } 30 31 public class Result_Entity 32 { 33 /// <summary> 34 /// 建议,有risky、pass、review三种值 35 /// </summary> 36 public string suggest { get; set; } 37 /// <summary> 38 /// 命中标签枚举值,100 正常;10001 广告;20001 时政;20002 色情;20003 辱骂;20006 违法犯罪;20008 欺诈;20012 低俗;20013 版权;21000 其他 39 /// </summary> 40 public int label { get; set; } 41 } 42 43 public class Detail_Entity 44 { 45 public string strategy { get; set; } 46 public int errcode { get; set; } 47 public string suggest { get; set; } 48 49 /// <summary> 50 /// 命中标签枚举值,100 正常;10001 广告;20001 时政;20002 色情;20003 辱骂;20006 违法犯罪;20008 欺诈;20012 低俗;20013 版权;21000 其他 51 /// </summary> 52 public int label { get; set; } 53 54 /// <summary> 55 /// 0-100,代表置信度,越高代表越有可能属于当前返回的标签(label) 56 /// </summary> 57 public int prob { get; set; } 58 public int level { get; set; } 59 public string keyword { get; set; } 60 } 61 62 } 63 #endregion 64 65 66 #endregion
测试:
正常返回:pass
 
{ "errcode": 0, "errmsg": "ok", "result": { "suggest": "pass", "label": 100 }, "detail": [ { "strategy": "keyword", "errcode": 0, "suggest": null, "label": 0, "prob": 0, "level": 0, "keyword": null }, { "strategy": "content_model", "errcode": 0, "suggest": "pass", "label": 100, "prob": 90, "level": 0, "keyword": null } ], "trace_id": "6836b8c9-378131c4-28e85c19" }
有风险的:risky
 
{ "errcode": 0, "errmsg": "ok", "result": { "suggest": "risky", "label": 20003 }, "detail": [ { "strategy": "keyword", "errcode": 0, "suggest": null, "label": 0, "prob": 0, "level": 0, "keyword": null }, { "strategy": "content_model", "errcode": 0, "suggest": "risky", "label": 20003, "prob": 90, "level": 0, "keyword": null } ], "trace_id": "6836b976-5808541a-78b3c137" }
过了第一关,下面我们还需要对评论情绪做个分析。不能让负面的评价随便显示😄,我们需要正能量。
实现情绪识别分类需要一个LLM大模型,不过考虑到资源的情况,我们能有免费的绝不花一毛钱,找不到免费的,或其他成本太高,我们就不考虑了。
使用ML.NET训练一个模型。ML.NET 是面向 .NET 开发人员的开源跨平台机器学习框架,支持将自定义机器学习模型集成到 .NET 应用程序中。
1、使用“模型生成器工具”,Visual Studio 扩展的图形化工具,用于生成、训练和部署自定义机器学习模型。

方案:数据分类。》环境:本地(CPU)
数据:文件或SQL Server都是可以的。Label标签就是要预测的列(使用1代表好评,0代表差评)。
训练:

评估:

使用:
 
//Load sample data var sampleData = new SentimentModel2.ModelInput() { Col0 = @"服务差", }; //Load model and predict output var result = SentimentModel2.Predict(sampleData);
2、API 方式
安装ML包
准备数据:格式要求评价内容 label标识(1好评,0差评)
运动鞋网面的黄渍居然洗干净了,鞋底的纹路里的泥垢也清得很彻底,太专业了! 1
洗衣店把我的衣服洗破了一个洞,只愿意退洗衣费,完全不赔偿衣服价值。 0
示例代码:训练模型并保存导出
 
static void ApiCreateAndTrainModel() { MLContext mLContext = new MLContext(); //加载数据 IDataView dataView = mLContext.Data.LoadFromTextFile<SentimentData>(_dataPath, hasHeader: false); //训练数据 //var pipeline = mLContext.Transforms.Text.FeaturizeText("Features", "SentimentText"); var pipeline = mLContext.Transforms.Text.FeaturizeText ( outputColumnName: "Features", inputColumnName: nameof(SentimentData.SentimentText) ) .Append(mLContext.BinaryClassification.Trainers.SdcaLogisticRegression(labelColumnName: "Label", featureColumnName: "Features")); //训练 var model = pipeline.Fit(dataView); //评估 IDataView predictions = model.Transform(dataView); CalibratedBinaryClassificationMetrics metrics = mLContext.BinaryClassification.Evaluate(predictions, "Label"); Console.WriteLine($"Accuracy: {metrics.Accuracy:P2}"); Console.WriteLine($"Auc: {metrics.AreaUnderRocCurve:P2}"); Console.WriteLine($"F1Score: {metrics.F1Score:P2}"); //预测 PredictionEngine<SentimentData, SentimentPrediction> predictionFunction = mLContext.Model.CreatePredictionEngine<SentimentData, SentimentPrediction>(model); SentimentData sampleStatement = new SentimentData { SentimentText = "店家说用了超声波清洗,难怪这么干净!" }; var resultPrediction = predictionFunction.Predict(sampleStatement); Console.WriteLine(JsonConvert.SerializeObject(resultPrediction)); Console.WriteLine($@"Sentiment: {resultPrediction.SentimentText} | Prediction: {(Convert.ToBoolean(resultPrediction.Prediction) ? "好评" : "差评")} | Probability: {resultPrediction.Probability} "); //保存 mLContext.Model.Save(model, dataView.Schema, "model.zip"); }
测试使用模型:
 
static readonly string _modelFile = Path.Combine(Environment.CurrentDirectory, "", @"model.zip"); var mlContext = new MLContext(); var model = mlContext.Model.Load(_modelFile, out var schema); var _predictionEngine = mlContext.Model.CreatePredictionEngine<SentimentData, SentimentPrediction>(model); var result = _predictionEngine.Predict(new SentimentData { SentimentText = "洗的非常干净" }); Console.WriteLine(JsonConvert.SerializeObject(result));
结果:
Accuracy: 98.92% Auc: 99.87% F1Score: 98.99% {"SentimentText":"洗的非常干净","Sentiment":false,"Prediction":true,"Probability":0.9957315,"Score":5.4522142}
开发:“总算折腾结束了,微信端的API,在结合模型的情感分析,基本能过滤掉大部分了。不过模型的准确性有很多影响因素:数据质量、算法选择、不同超参数。模型也是一个迭代的过程。”
内心OS:“初期模型质量不达标,可以结合业务反选嘛,评论默认不显示,准确识别通过后显示” 😙
总结:
产品&业务:这个方案挺好,微信的API是免费的,管理后台操作功能齐全,AI识别情绪筛选,技术开发自己搞定,不用投入钱,快速上线,出了问题甩锅方便。
😪😪😪 结束。
 
                     
                    
                 
                    
                 
 
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号