资讯动态

xllg 零工评价:双向匿名 + 信誉分实时刷新,这条链路怎么搭

发布时间:2026/10/15 9:48:11 来源:尧图企业网站定制
零工平台最怕的就是干完活没人说好坏。xllg 的评价体系上线时定了个规则双向匿名——工人评价雇主、雇主评价工人双方都看不到是谁写的只看到评分和内容。匿名是为了防报复但匿名又带来新问题没法人工核对数据质量全靠程序兜。这条链路怎么搭下面拆开讲。评价表的落点评价是订单的附属一单一评双向各一条。表结构里两个关键设计is_anonymous匿名标记和score评分还有一个source_type区分是工人评雇主还是雇主评工人CREATETABLEjx_evaluate(idbigintNOTNULLAUTO_INCREMENTCOMMENT主键,order_idbigintNOTNULLCOMMENT订单ID,evaluate_typetinyintNOTNULLCOMMENT1工人评雇主 2雇主评工人,from_user_idbigintNOTNULLCOMMENT评价人,to_user_idbigintNOTNULLCOMMENT被评价人,scoretinyintNOTNULLCOMMENT评分1-5,contentvarchar(500)DEFAULTNULLCOMMENT评价内容,is_anonymoustinyintNOTNULLDEFAULT1COMMENT是否匿名1匿名 0实名,create_timedatetimeDEFAULTNULLCOMMENT创建时间,UNIQUEKEYuk_order_type(order_id,evaluate_type))ENGINEInnoDBDEFAULTCHARSETutf8mb4COMMENT双向评价表;uk_order_type联合唯一索引保证一单一个方向只能评一次重复提交直接报错连查重逻辑都省了。匿名怎么实现查询层脱掉身份匿名不是不给 from_user_id而是展示层不暴露。查询评价列表时如果is_anonymous1把 from_user_id 换成固定占位比如 -1头像换成默认图昵称显示匿名用户publicListlistEvaluate(LongtoUserId){// 只查被评价人的记录ListlistevaluateMapper.selectList(Wrappers.lambdaQuery().eq(Evaluate::getToUserId,toUserId).orderByDesc(Evaluate::getCreateTime).last(limit 20));returnlist.stream().map(e-{EvaluateVOvonewEvaluateVO();BeanUtils.copyProperties(e,vo);if(Integer.valueOf(1).equals(e.getIsAnonymous())){vo.setFromUserId(-1L);vo.setFromNickname(匿名用户);vo.setFromAvatar(Constants.DEFAULT_AVATAR);}else{// 实名评价才查用户昵称头像UseruseruserService.getById(e.getFromUserId());vo.setFromNickname(user.getNickname());vo.setFromAvatar(user.getAvatar());}returnvo;}).collect(Collectors.toList());}注意一个细节写库时照样存真实的 from_user_id。万一有纠纷运营后台需要查得到是谁评的——匿名是对外展示不是对平台隐藏。后台查询接口和前端接口分开后台接口直接返回原始数据。信誉分评价落地后实时重算每个用户一个jx_user_credit表存信用分和评价统计。评分 1-5 分转成信用分5 分 2、4 分 1、3 分 0、1-2 分 -3。评价落库后在同一个事务里更新统计TransactionalpublicvoidsubmitEvaluate(EvaluateCmdcmd){// 1. 校验订单状态已完结才能评未完结直接拒绝OrderorderorderMapper.selectById(cmd.getOrderId());if(ordernull||order.getStatus()!OrderStatus.FINISHED){thrownewBizException(订单未完结不能评价);}// 2. 插入评价联合唯一索引兜底防重复EvaluateevaluateEvaluate.builder().orderId(cmd.getOrderId()).evaluateType(cmd.getEvaluateType()).fromUserId(cmd.getFromUserId()).toUserId(cmd.getToUserId()).score(cmd.getScore()).content(cmd.getContent()).isAnonymous(cmd.getIsAnonymous()).build();evaluateMapper.insert(evaluate);// 3. 重算被评价人的信誉分同一个事务失败一起回滚UserCreditcreditcreditMapper.selectByUserIdForUpdate(cmd.getToUserId());intdeltaswitch(cmd.getScore()){case5-2;case4-1;case3-0;default--3;};credit.setScore(credit.getScore()delta);credit.setEvaluateCount(credit.getEvaluateCount()1);credit.setAvgScore((credit.getAvgScore()*(credit.getEvaluateCount()-1)cmd.getScore())/credit.getEvaluateCount());creditMapper.updateById(credit);}selectByUserIdForUpdate是带FOR UPDATE的查询同一时刻两个人同时给同一用户评价串行执行分数不会算丢。踩坑实录信誉分越改越少最后变负数了现象上线第三天运营发现一个干了几十单的老工人信誉分变成了 -6下面却挂着二十多条 5 星好评。用户打电话来问我好评这么多分怎么是负的。排查过程先看日志这个用户确实收到了 20 条评价但信用分在某一时刻突然从 10 跳到 -3之后每次评价都是 2 却再也回不来。怀疑是初始化逻辑的问题。翻初始化代码用户注册时jx_user_credit插入一条 score0 的记录用的是INSERT ... ON DUPLICATE KEY UPDATE。定位思路问题出在平均分的计算顺序上。代码里先evaluateCount 1再算 avgScore公式里用的是更新后的 count但第一次评价时evaluateCount从 0 变 1avgScore分子是0 * 0 score分母却是 1看起来没错。再往下看并发时 FOR UPDATE 锁住了行但 upsert 初始化那步没锁。两个用户同时给同一人评价其中一人走到selectByUserIdForUpdate时记录还没创建返回 null直接 NPE 被全局异常吞掉事务回滚。而另一条路径里creditMapper.updateById(null)竟然更新了 0 行不报错——重复初始化把 score 重置回了 0后续 2 从 0 开始。最终解决初始化改成注册时同步显式插入不走 upsert评价方法里selectByUserIdForUpdate返回 null 时直接抛业务异常提示用户信誉数据缺失请联系管理员不再静默吞掉。再把 avgScore 的计算挪到 count1 之前用旧 count 算分子分母。上线后跑了一周没有再出现分数异常。可以直接抄的清单双向匿名评价uk(order_id, evaluate_type)唯一索引防重复匿名只在展示层脱敏库底留真实身份信誉分实时重算必须和评价落库同事务FOR UPDATE锁行防并发算丢初始化记录要显式插入别用ON DUPLICATE KEY UPDATE偷懒——首评并发时会炸平均分公式用旧 countavg (avg * oldCount newScore) / (oldCount 1)好评多但分数负优先怀疑初始化/upsert 路径而不是业务公式项目源码https://gitee.com/gzqkl/xllg

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑