TH10-数据统计与内容审核

news2024/11/16 0:00:57

TH10-数据统计与内容审核

    • 1、用户冻结解冻
      • 1.1 用户冻结
        • ManageController
        • ManageService
      • 1.2 用户解冻
        • ManageController
        • ManageService
      • 1.3 查询数据列表
        • UserInfo
        • ManageService
        • 1.4 探花系统修改
        • UserFreezeService
    • 2、数据统计
      • 2.1 数据采集
        • 2.1.1 部署RabbitMQ
        • 2.1.2 消息类型说明
        • 2.1.3 实体类对象
          • Log
          • Analysis
        • 2.1.4 发送日志消息
        • 2.1.5 监听器处理消息
        • 2.1.6 消息发送工具类
      • 2.2 AOP处理日志
        • 2.2.1 自定义注解
        • 2.2.2 切面
        • 2.2.3 配置
      • 2.3 定时任务
        • 2.3.1 入门案例
          • 开启定时任务
          • 定时任务类
        • 2.3.2 CRON表达式
        • 2.3.3 定时统计
          • AnalysisTask
          • AnalysisService
          • AnalysisMapper
          • 测试数据
      • 2.4 首页统计
        • 2.4.1 vo对象
        • 2.4.2 DashboardController
        • 2.4.3 AnalysisService
        • 2.4.4 AnalysisMapper
    • 3、内容审核
      • 3.1 阿里云内容审核
        • 3.1.1 准备工作
        • 3.1.2 文本内容垃圾检测
        • 3.1.3 图片审核
        • 3.1.4 抽取工具
          • GreenProperties
          • AliyunGreenTemplate
          • TanhuaAutoConfiguration
          • 配置文件
          • 单元测试
      • 3.2 动态审核
        • 3.2.1 执行流程
        • 3.2.2 发布消息
        • 3.2.3 监听器
        • 3.2.4 movementApi

1、用户冻结解冻

用户冻结/解冻使用管理员在后台系统对用户的惩罚措施。对于发布不当言论或者违法违规内容的用户,可以暂时、永久禁止其登录,评论,发布动态、

后台中解冻/冻结,就是将用户状态写入数据库中

APP端用户在进行登录,评论,发布动态时检测Redis中冻结状态

在这里插入图片描述

1.1 用户冻结

ManageController

//用户冻结
@PostMapping("/users/freeze")
public ResponseEntity freeze(@RequestBody Map params) {
    Map map =  managerService.userFreeze(params);
    return ResponseEntity.ok(map);
}

ManageService

//用户冻结
public Map userFreeze(Map params) {
    Integer freezingTime = (Integer) params.get("freezingTime");
    Long userId = (Long) params.get("userId");
    int days = 0;
    if (freezingTime == 1) {
        days = 3;
    }
    if (freezingTime == 2) {
        days = 7;
    }
    if (freezingTime == 3) {
        days = -1;
    }
    String value = JSON.toJSONString(params);
    redisTemplate.opsForValue().set(Constants.FREEZE_USER+userId,value,days, TimeUnit.MINUTES);
    Map map = new HashMap();
    map.put("message","冻结成功");
    return map;
}

1.2 用户解冻

ManageController

//用户解冻
@PostMapping("/users/unfreeze")
public ResponseEntity unfreeze(@RequestBody  Map params) {
    Map map =  managerService.userUnfreeze(params);
    return ResponseEntity.ok(map);
}

ManageService

//用户解冻
public Map userUnfreeze(Map params) {
    Long userId = (Long) params.get("userId");
    String reasonsForThawing = (String) params.get("reasonsForThawing");
    redisTemplate.delete(Constants.FREEZE_USER+userId);
    Map map = new HashMap();
    map.put("message","解冻成功");
    return map;
}

1.3 查询数据列表

UserInfo

添加字段

//用户状态,1为正常,2为冻结
@TableField(exist = false)
private String userStatus = "1";

ManageService

public ResponseEntity findById(Long userId) {
    UserInfo info = userInfoApi.findById(userId);
    if(redisTemplate.hasKey(Constants.FREEZE_USER+info.getId())) {
        info.setUserStatus("2");
    }
    return ResponseEntity.ok(info);
}

1.4 探花系统修改

UserFreezeService

@Service
public class UserFreezeService {

    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    public void checkUserStatus(Integer state,Long userId) {
        String value = redisTemplate.opsForValue().get(Constants.FREEZE_USER + userId);
        if(!StringUtils.isEmpty(value)) {
            Map map = JSON.parseObject(value, Map.class);
            Integer freezingRange = (Integer) map.get("freezingRange");
            if(freezingRange == state) {
                throw new BusinessException(ErrorResult.builder().errMessage("您的账号被冻结!").build());
            }
        }
    }
}

2、数据统计

后台系统首页中,显示各种统计数据,比如:累计用户数、新增用户数、登录次数等内容。

1、探花系统将用户操作日志写入RabbitMQ

2、管理后台获取最新消息,构造日志数据存入数据库

3、加入统计表,定时统计

在这里插入图片描述

2.1 数据采集

1、探花系统将用户操作日志写入RabbitMQ

2、管理后台获取最新消息,构造日志数据存入数据库

2.1.1 部署RabbitMQ

探花交友所需的第三方服务组件,已经以Docker-Compose准备好了。仅仅需要进入相关目录,以命令形式启动运行即可

#进入目录
cd /root/docker-file/rmq/
#创建容器并启动
docker-compose up –d
#查看容器
docker ps -a

服务地址:192.168.136.160:5672

管理后台:http://192.168.136.160:15672/

2.1.2 消息类型说明

探花项目间使用RabbitMQ收发消息,这里采用topic类型消息

日志消息key规则:log.xxx

在这里插入图片描述

2.1.3 实体类对象

Log
package com.tanhua.model.domain;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Log {
    /**
     * id
     */
    private Long id;
    /**
     * 用户id
     */
    private Long userId;
    /**
     * 操作时间
     */
    private String logTime;

    /**
     * 操作类型,
     * 0101为登录,0102为注册,
     * 0201为发动态,0202为浏览动态,0203为动态点赞,0204为动态喜欢,0205为评论,0206为动态取消点赞,0207为动态取消喜欢,
     * 0301为发小视频,0302为小视频点赞,0303为小视频取消点赞,0304为小视频评论
     */
    private String type;

    /**
     * 登陆地点
     */
    private String place;
    /**
     * 登陆设备
     */
    private String equipment;

    public Log(Long userId, String logTime, String type) {
        this.userId = userId;
        this.logTime = logTime;
        this.type = type;
    }
}
Analysis
package com.tanhua.model.domain;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Analysis{

    private Long id;
    /**
     * 日期
     */
    private Date recordDate;
    /**
     * 新注册用户数
     */
    private Integer numRegistered = 0;
    /**
     * 活跃用户数
     */
    private Integer numActive = 0;
    /**
     * 登陆次数
     */
    private Integer numLogin = 0;
    /**
     * 次日留存用户数
     */
    private Integer numRetention1d = 0;

    private Date created;
}

2.1.4 发送日志消息

@Autowired
private AmqpTemplate amqpTemplate;
public void login() {
    ………    
    //构造Map集合   
    Map<String, Object> msg = new HashMap<>();   
    msg.put(“userId”, UserHolder.getUserId().toString());  
    msg.put(“date", System.currentTimeMillis());    
	msg.put("type", "0101",);   
    String message = JSON.toJSONString(msg);    
    //发送消息
 	try {      
		amqpTemplate.convertSendAndReceive("tanhua.log.exchange",
                                             "log.user",message);    
	}catch (Exception e) {
		e.printStackTrace();
	}  
	………
}

2.1.5 监听器处理消息

@Component
public class LogListener {

    @Autowired
    private LogMapper logMapper;

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(
                    value = "tanhua.log.queue",
                    durable = "true"
            ),
            exchange = @Exchange(
                    value = "tanhua.log.exchange",
                    type = ExchangeTypes.TOPIC),
            key = {"log.*"})
    )
    public void listenCreate(String message) throws Exception {
        try {
            Map<String, Object> map = JSON.parseObject(message);
            //1、获取数据
            Long userId = (Long) map.get("userId");
            String date = (String) map.get("date");
            String objId = (String) map.get("objId");
            String type = (String) map.get("type");
            //2、保存到数据库
            Log log = new Log(userId,date,type);
            logMapper.insert(log);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2.1.6 消息发送工具类

package com.tanhua.server.service;

import com.alibaba.fastjson.JSON;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Service
public class MqMessageService {

    @Autowired
    private AmqpTemplate amqpTemplate;

    //发送日志消息
    public void sendLogService(Long userId,String type,String key,String busId) {
        try {
            Map map = new HashMap();
            map.put("userId",userId.toString());
            map.put("type",type);
            map.put("logTime",new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
            map.put("busId",busId);
            String message = JSON.toJSONString(map);
            amqpTemplate.convertAndSend("tanhua.log.exchange",
                    "log."+key,message);
        } catch (AmqpException e) {
            e.printStackTrace();
        }
    }

    //发送动态审核消息
    public void sendAudiService(String movementId) {
        try {
            amqpTemplate.convertAndSend("tanhua.audit.exchange",
                    "audit.movement",movementId);
        } catch (AmqpException e) {
            e.printStackTrace();
        }
    }
}

2.2 AOP处理日志

项目中大量方法需要改造,加入消息处理。

不易维护且存在耦合

解决方法:使用AOP + 自定义注解

2.2.1 自定义注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogConfig {

    //动态获取方法参数,支持SpringEL
    String objId() default "";

    //路由的key
    String key();

    //日志类型
    String type();
}

2.2.2 切面

@Component
@Aspect
public class LogAspect {

    @Autowired
    private AmqpTemplate amqpTemplate;

    @Before(value="execution(* com.tanhua.server.service.*.*(..)) && @annotation(config)")
    public void checkUserState(JoinPoint pjp , LogConfig config) throws Throwable {
        //解析SpringEL获取动态参数
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        String objId = parse(config.objId(), signature.getParameterNames(), pjp.getArgs());
        //构造Map集合
        Map<String, Object> msg = new HashMap<>();
        msg.put("userId", UserHolder.getUserId());
        msg.put("date", new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
        msg.put("objId", objId);
        msg.put("type", config.type());
        String message = JSON.toJSONString(msg);
        //发送消息
        try {
            amqpTemplate.convertSendAndReceive("tanhua.log.exchange",
                    "log."+config.key(),message);
        }catch (Exception e) {
            e.printStackTrace();
        }
    }


    public String parse(String expression, String[] paraNames,Object [] paras) {
        if(StringUtils.isEmpty(expression)) return "";
        StandardEvaluationContext context = new StandardEvaluationContext();
        for(int i=0;i<paraNames.length;i++) {
            context.setVariable(paraNames[i], paras[i]);
        }
        Expression exp = new SpelExpressionParser().parseExpression(expression);
        Object value = exp.getValue(context);
        return value == null ? "" : value.toString();
    }
}

2.2.3 配置

//根据id查询
@LogConfig(type = "0202",key = "movement",objId = "#movementId")
public MovementsVo findById(String movementId) {
    //1、调用api根据id查询动态详情
    Movement movement = movementApi.findById(movementId);
    //2、转化vo对象
    if(movement != null) {
        UserInfo userInfo = userInfoApi.findById(movement.getUserId());
        return MovementsVo.init(userInfo,movement);
    }else {
        return null;
    }
}

2.3 定时任务

在实际项目开发中,除了Web应用、SOA服务外,还有一类不可缺少的,那就是定时任务调度。定时任务的场景可以说非常广泛:

  • 某些网站会定时发送优惠邮件;

  • 银行系统还款日信用卡催收款;

  • 某些应用的生日祝福短信等。

那究竟何为定时任务调度,一句话概括就是:基于给定的时间点、给定的时间间隔、自动执行的任务

2.3.1 入门案例

开启定时任务
@SpringBootApplication(exclude = {MongoAutoConfiguration.class, MongoDataAutoConfiguration.class})
@MapperScan("com.tanhua.admin.mapper")
@EnableScheduling //开启定时任务支持
public class AdminServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(AdminServerApplication.class,args);
    }
}
定时任务类
@Component
public class AnalysisTask {
    /**
     * 配置时间规则
     */
    @Scheduled( cron = "0/20 * * * * ? ")
    public void analysis() throws ParseException {
        //业务逻辑
        String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
        System.out.println("当前时间:"+time);
    }
}

2.3.2 CRON表达式

对于定时任务,我们使用的时候主要是注重两个方面,一个是定时任务的业务,另一个就是Cron表达式。

**Cron 表达式支持到六个域 **

名称是否必须允许值特殊字符
0-59, - * /
0-59, - * /
0-23, - * /
1-31, - * ? / L W C
1-12 或 JAN-DEC, - * /
1-7 或 SUN-SAT, - * ? / L C #

月份和星期的名称是不区分大小写的。FRI 和 fri 是一样的。 域之间有空格分隔

2.3.3 定时统计

AnalysisTask
@Component
public class AnalysisTask {

    @Autowired
    private AnalysisService analysisService;

    /**
     * 配置时间规则
     */
    @Scheduled( cron = "0/20 * * * * ? ")
    public void analysis() throws ParseException {
        //业务逻辑
        String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
        System.out.println("开始统计:"+time);
        //调logService完成日志统计
        analysisService.analysis();
        System.out.println("结束统计");
    }
}
AnalysisService
/**
 * 定时统计日志数据到统计表中
 *    1、查询tb_log表中的数 (每日注册用户数,每日登陆用户,活跃的用户数据,次日留存的用户)
 *    2、构造AnalysisByDay对象
 *    3、完成统计数据的更新或者保存
 */
public void analysis() throws ParseException {

    String todayStr = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
    String yestodayStr =  DateUtil.yesterday().toString("yyyy-MM-dd"); //工具类
    //1、统计每日注册用户数
    Integer numRegistered = logMapper.queryByTypeAndLogTime("0102",todayStr);
    //2、统计每日登陆用户
    Integer numLogin = logMapper.queryByTypeAndLogTime("0101",todayStr);
    //3、统计活跃的用户数
    Integer numActive = logMapper.queryByLogTime(todayStr);
    //4、统计次日留存的用户数
    Integer numRetention1d = logMapper.queryNumRetention1d(todayStr, yestodayStr);
    //5、根据当前时间查询AnalysisByDay数据
    QueryWrapper<Analysis> qw = new QueryWrapper<>();
    Date todatDate = new SimpleDateFormat("yyyy-MM-dd").parse(todayStr);
    qw.eq("record_date", todatDate);

    Analysis analysis = analysisMapper.selectOne(qw);
    if(analysis == null) {
        //7、如果不存在,保存
        analysis = new Analysis();
        analysis.setRecordDate(todatDate);
        analysis.setNumRegistered(numRegistered);
        analysis.setNumLogin(numLogin);
        analysis.setNumActive(numActive);
        analysis.setNumRetention1d(numRetention1d);
        analysis.setCreated(new Date());
        analysisMapper.insert(analysis);
    }else{
        //8、如果存在,更新
        analysis.setNumRegistered(numRegistered);
        analysis.setNumLogin(numLogin);
        analysis.setNumActive(numActive);
        analysis.setNumRetention1d(numRetention1d);
        analysisMapper.updateById(analysis);
    }

}
AnalysisMapper
public interface LogMapper extends BaseMapper<Log> {
    @Select("SELECT COUNT(DISTINCT user_id) FROM tb_log WHERE TYPE=#{type} AND log_time=#{logTime}")
    Integer queryByTypeAndLogTime(@Param("type") String type, @Param("logTime") String logTime); //根据操作时间和类型


    @Select("SELECT COUNT(DISTINCT user_id) FROM tb_log WHERE log_time=#{logTime}")
    Integer queryByLogTime(String logTime); //展示记录时间查询


    @Select("SELECT COUNT(DISTINCT user_id)  FROM tb_log WHERE log_time=#{today} AND user_id IN (\n " +
        " SELECT user_id FROM tb_log WHERE TYPE=\"0102\" AND log_time=#{yestoday} \n " +
        ")")
    Integer queryNumRetention1d(@Param("today")  String today,@Param("yestoday") String yestoday); //查询次日留存
}
测试数据

为了方便操作,可以通过以下单元测试方法。保存若干操作数据

package com.tanhua.manager.test;

import com.tanhua.manager.domain.Log;
import com.tanhua.manager.mapper.LogMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Random;

@RunWith(SpringRunner.class)
@SpringBootTest
public class LogTest {

    @Autowired
    private LogMapper logMapper;
    
    private String logTime = "";

    //模拟登录数据
    public void testInsertLoginLog() {
        for (int i = 0; i < 5; i++) {
            Log log = new Log();
            log.setUserId((long)(i+1));
            log.setLogTime(logTime);
            log.setType("0101");
            logMapper.insert(log);
        }
    }

    //模拟注册数据
    public void testInsertRegistLog() {
        for (int i = 0; i < 10; i++) {
            Log log = new Log();
            log.setUserId((long)(i+1));
            log.setLogTime(logTime);
            log.setType("0102");
            logMapper.insert(log);
        }
    }
    //模拟其他操作
    public void testInsertOtherLog() {
        String[] types = new String[]{"0201","0202","0203","0204","0205","0206","0207","0301","0302","0303","0304"};
        for (int i = 0; i < 10; i++) {
            Log log = new Log();
            log.setUserId((long)(i+1));
            log.setLogTime(logTime);
            int index = new Random().nextInt(10);
            log.setType(types[index]);
            logMapper.insert(log);
        }
    }

	@Test
    public void generData() {
        testInsertLoginLog();
        testInsertRegistLog();
        testInsertOtherLog();
    }
}

2.4 首页统计

在这里插入图片描述

2.4.1 vo对象

@Data
@AllArgsConstructor
@NoArgsConstructor
public class AnalysisSummaryVo {
    /**
     * 累计用户数
     */
    private Long cumulativeUsers;
    /**
     * 过去30天活跃用户数
     */
    private Long activePassMonth;
    /**
     * 过去7天活跃用户
     */
    private Long activePassWeek;
    /**
     * 今日新增用户数量
     */
    private Long newUsersToday;
    /**
     * 今日新增用户涨跌率,单位百分数,正数为涨,负数为跌
     */
    private BigDecimal newUsersTodayRate;
    /**
     * 今日登录次数
     */
    private Long loginTimesToday;
    /**
     * 今日登录次数涨跌率,单位百分数,正数为涨,负数为跌
     */
    private BigDecimal loginTimesTodayRate;
    /**
     * 今日活跃用户数量
     */
    private Long activeUsersToday;
    /**
     * 今日活跃用户涨跌率,单位百分数,正数为涨,负数为跌
     */
    private BigDecimal activeUsersTodayRate;
}

2.4.2 DashboardController

@RestController
@RequestMapping("/dashboard")
public class DashboardController {

    @Autowired
    private AnalysisService analysisService;

    /**
     * 概要统计信息
     */
    @GetMapping("/dashboard/summary")
    public AnalysisSummaryVo getSummary() {

        AnalysisSummaryVo analysisSummaryVo = new AnalysisSummaryVo();

        DateTime dateTime = DateUtil.parseDate("2020-09-08");

        //累计用户数
        analysisSummaryVo.setCumulativeUsers(Long.valueOf(1000));

        //过去30天活跃用户
        analysisSummaryVo.setActivePassMonth(this.analysisService.queryActiveUserCount(dateTime, -30));

        //过去7天活跃用户
        analysisSummaryVo.setActivePassWeek(this.analysisService.queryActiveUserCount(dateTime, -7));

        //今日活跃用户
        analysisSummaryVo.setActiveUsersToday(this.analysisService.queryActiveUserCount(dateTime, 0));


        //今日新增用户
        analysisSummaryVo.setNewUsersToday(this.analysisService.queryRegisterUserCount(dateTime, 0));

        //今日新增用户涨跌率,单位百分数,正数为涨,负数为跌
        analysisSummaryVo.setNewUsersTodayRate(computeRate(
                analysisSummaryVo.getNewUsersToday(),
                this.analysisService.queryRegisterUserCount(dateTime, -1)
        ));

        //今日登录次数
        analysisSummaryVo.setLoginTimesToday(this.analysisService.queryLoginUserCount(dateTime, 0));

        //今日登录次数涨跌率,单位百分数,正数为涨,负数为跌
        analysisSummaryVo.setLoginTimesTodayRate(computeRate(
                analysisSummaryVo.getLoginTimesToday(),
                this.analysisService.queryLoginUserCount(dateTime, -1)
        ));


        return analysisSummaryVo;

    }

    private static BigDecimal computeRate(Long current, Long last) {
        BigDecimal result;
        if (last == 0) {
            // 当上一期计数为零时,此时环比增长为倍数增长
            result = new BigDecimal((current - last) * 100);
        } else {
            result = BigDecimal.valueOf((current - last) * 100).divide(BigDecimal.valueOf(last), 2, BigDecimal.ROUND_HALF_DOWN);
        }
        return result;
    }
    
    private static String offsetDay(Date date,int offSet) {
        return DateUtil.offsetDay(date,offSet).toDateStr();
    }
}

2.4.3 AnalysisService

/**
 * 查询活跃用户的数量
 */
public Long queryActiveUserCount(DateTime today, int offset) {
    return this.queryUserCount(today, offset, "num_active");
}

/**
 * 查询注册用户的数量
 */
public Long queryRegisterUserCount(DateTime today, int offset) {
    return this.queryUserCount(today, offset, "num_registered");
}

/**
 * 查询登录用户的数量
 */
public Long queryLoginUserCount(DateTime today, int offset) {
    return this.queryUserCount(today, offset, "num_login");
}

private Long queryAnalysisCount(String column,String today,String offset){
    return analysisMapper.sumAnalysisData(column,leDate,dtDate);
}

2.4.4 AnalysisMapper

public interface AnalysisMapper extends BaseMapper<Analysis> {

    @Select("select sum(${column}) from tb_analysis where record_date > #{leDate} and record_date < #{gtDate}")
    Long sumAnalysisData(@Param("column") String column, @Param("leDate") String leDate, @Param("gtDate") String gtDate);
}

3、内容审核

内容安全是识别服务,支持对图片、视频、文本、语音等对象进行多样化场景检测,有效降低内容违规风险。

目前很多平台都支持内容检测,如阿里云、腾讯云、百度AI、网易云等国内大型互联网公司都对外提供了API。

按照性能和收费来看,探花交友项目使用的就是阿里云的内容安全接口,使用到了图片和文本的审核。

3.1 阿里云内容审核

3.1.1 准备工作

1,前往阿里云官网注册账号

2,打开云盾内容安全产品试用页面,单击立即开通,正式开通服务

在这里插入图片描述

3,在AccessKey管理页面管理您的AccessKeyID和AccessKeySecret

在这里插入图片描述

3.1.2 文本内容垃圾检测

文本垃圾内容检测:点击访问

文本垃圾内容Java SDK: 点击访问

在这里插入图片描述

3.1.3 图片审核

在这里插入图片描述

图片垃圾内容Java SDK: https://help.aliyun.com/document_detail/53424.html?spm=a2c4g.11186623.6.715.c8f69b12ey35j4

3.1.4 抽取工具

GreenProperties
@Data
@ConfigurationProperties("tanhua.green")
public class GreenProperties {
    /**
     * 账号
     */
    String accessKeyID;
    /**
     * 密钥
     */
    String accessKeySecret;

    /**
     * 场景
     */
    String scenes;
}
AliyunGreenTemplate
package com.tanhua.autoconfig.template;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.green.model.v20180509.ImageSyncScanRequest;
import com.aliyuncs.green.model.v20180509.TextScanRequest;
import com.aliyuncs.http.FormatType;
import com.aliyuncs.http.HttpResponse;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.http.ProtocolType;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import com.tanhua.autoconfig.properties.GreenProperties;
import lombok.extern.slf4j.Slf4j;

import java.util.*;

/**
 * @author: itheima
 * @create: 2021-05-31 00:46
 */
@Slf4j
public class AliyunGreenTemplate {

    private IAcsClient client;

    private GreenProperties greenProperties;

    public AliyunGreenTemplate(GreenProperties greenProperties) {
        this.greenProperties = greenProperties;
        try {
            IClientProfile profile = DefaultProfile
                    .getProfile("cn-shanghai", greenProperties.getAccessKeyID(), greenProperties.getAccessKeySecret());
            DefaultProfile
                    .addEndpoint("cn-shanghai", "cn-shanghai", "Green", "green.cn-shanghai.aliyuncs.com");
            client = new DefaultAcsClient(profile);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("Green配置缺失,请补充!");
        }
    }


    /**
     * 阿里云文本内容检查
     *
     * @param content
     * @return map  key - suggestion内容
     * pass:文本正常,可以直接放行,
     * review:文本需要进一步人工审核,
     * block:文本违规,可以直接删除或者限制公开
     * value -   通过,或 出错原因
     * @throws Exception
     */
    public Map<String, String> greenTextScan(String content) throws Exception {
        TextScanRequest textScanRequest = new TextScanRequest();
        textScanRequest.setAcceptFormat(FormatType.JSON); // 指定api返回格式
        textScanRequest.setHttpContentType(FormatType.JSON);
        textScanRequest.setMethod(MethodType.POST); // 指定请求方法
        textScanRequest.setEncoding("UTF-8");
        textScanRequest.setRegionId("cn-shanghai");
        List<Map<String, Object>> tasks = new ArrayList<>();
        Map<String, Object> task1 = new LinkedHashMap<>();
        task1.put("dataId", UUID.randomUUID().toString());
        /**
         * 待检测的文本,长度不超过10000个字符
         */
        task1.put("content", content);
        tasks.add(task1);
        JSONObject data = new JSONObject();

        /**
         * 检测场景,文本垃圾检测传递:antispam
         **/
        data.put("scenes", Arrays.asList("antispam"));
        data.put("tasks", tasks);
        log.info("检测任务内容:{}", JSON.toJSONString(data, true));
        textScanRequest.setHttpContent(data.toJSONString().getBytes("UTF-8"), "UTF-8", FormatType.JSON);
        // 请务必设置超时时间
        textScanRequest.setConnectTimeout(3000);
        textScanRequest.setReadTimeout(6000);

//        返回结果内容
        Map<String, String> resultMap = new HashMap<>();
        try {
            HttpResponse httpResponse = client.doAction(textScanRequest);
            if (!httpResponse.isSuccess()) {
                new RuntimeException("阿里云文本内容检查出现异常!");
            }
            JSONObject scrResponse = JSON.parseObject(new String(httpResponse.getHttpContent(), "UTF-8"));
            log.info("检测结果内容:{}", JSON.toJSONString(scrResponse, true));
            if (200 != scrResponse.getInteger("code")) {
                new RuntimeException("阿里云文本内容检查出现异常!");
            }
            JSONArray taskResults = scrResponse.getJSONArray("data");
            for (Object taskResult : taskResults) {
                if (200 != ((JSONObject) taskResult).getInteger("code")) {
                    new RuntimeException("阿里云文本内容检查出现异常!");
                }
                JSONArray sceneResults = ((JSONObject) taskResult).getJSONArray("results");
                for (Object sceneResult : sceneResults) {
                    String scene = ((JSONObject) sceneResult).getString("scene");
                    String label = ((JSONObject) sceneResult).getString("label");
                    String suggestion = ((JSONObject) sceneResult).getString("suggestion");
                    log.info("最终内容检测结果,suggestion = {},label={}", suggestion, label);
//                    设置默认错误返回内容
                    resultMap.put("suggestion", suggestion);
                    if (suggestion.equals("review")) {
                        resultMap.put("reson", "文章内容中有不确定词汇");
                        log.info("返回结果,resultMap={}", resultMap);
                        return resultMap;
                    } else if (suggestion.equals("block")) {
                        String reson = "文章内容中有敏感词汇";
                        if (label.equals("spam")) {
                            reson = "文章内容中含垃圾信息";
                        } else if (label.equals("ad")) {
                            reson = "文章内容中含有广告";
                        } else if (label.equals("politics")) {
                            reson = "文章内容中含有涉政";
                        } else if (label.equals("terrorism")) {
                            reson = "文章内容中含有暴恐";
                        } else if (label.equals("abuse")) {
                            reson = "文章内容中含有辱骂";
                        } else if (label.equals("porn")) {
                            reson = "文章内容中含有色情";
                        } else if (label.equals("flood")) {
                            reson = "文章内容灌水";
                        } else if (label.equals("contraband")) {
                            reson = "文章内容违禁";
                        } else if (label.equals("meaningless")) {
                            reson = "文章内容无意义";
                        }
                        resultMap.put("reson", reson);
                        log.info("返回结果,resultMap={}", resultMap);
                        return resultMap;
                    }

                }
            }
            resultMap.put("suggestion", "pass");
            resultMap.put("reson", "检测通过");

        } catch (Exception e) {
            log.error("阿里云文本内容检查出错!");
            e.printStackTrace();
            new RuntimeException("阿里云文本内容检查出错!");
        }
        log.info("返回结果,resultMap={}", resultMap);
        return resultMap;
    }

    /**
     * 阿里云图片内容安全
     */
    public Map imageScan(List<String> imageList) throws Exception {
        IClientProfile profile = DefaultProfile
                .getProfile("cn-shanghai", greenProperties.getAccessKeyID(), greenProperties.getAccessKeySecret());
        ImageSyncScanRequest imageSyncScanRequest = new ImageSyncScanRequest();
        // 指定api返回格式
        imageSyncScanRequest.setAcceptFormat(FormatType.JSON);
        // 指定请求方法
        imageSyncScanRequest.setMethod(MethodType.POST);
        imageSyncScanRequest.setEncoding("utf-8");
        //支持http和https
        imageSyncScanRequest.setProtocol(ProtocolType.HTTP);
        JSONObject httpBody = new JSONObject();
        /**
         * 设置要检测的场景, 计费是按照该处传递的场景进行
         * 一次请求中可以同时检测多张图片,每张图片可以同时检测多个风险场景,计费按照场景计算
         * 例如:检测2张图片,场景传递porn、terrorism,计费会按照2张图片鉴黄,2张图片暴恐检测计算
         * porn: porn表示色情场景检测
         */

        httpBody.put("scenes", Arrays.asList(greenProperties.getScenes().split(",")));

        /**
         * 如果您要检测的文件存于本地服务器上,可以通过下述代码片生成url
         * 再将返回的url作为图片地址传递到服务端进行检测
         */
        /**
         * 设置待检测图片, 一张图片一个task
         * 多张图片同时检测时,处理的时间由最后一个处理完的图片决定
         * 通常情况下批量检测的平均rt比单张检测的要长, 一次批量提交的图片数越多,rt被拉长的概率越高
         * 这里以单张图片检测作为示例, 如果是批量图片检测,请自行构建多个task
         */
        List list = new ArrayList();
        for (String imageUrl : imageList) {
            JSONObject task = new JSONObject();
            task.put("dataId", UUID.randomUUID().toString());
            // 设置图片链接。
            task.put("url", imageUrl);
            task.put("time", new Date());
            list.add(task);
        }

        httpBody.put("tasks",list);

        imageSyncScanRequest.setHttpContent(org.apache.commons.codec.binary.StringUtils.getBytesUtf8(httpBody.toJSONString()),
                "UTF-8", FormatType.JSON);
        /**
         * 请设置超时时间, 服务端全链路处理超时时间为10秒,请做相应设置
         * 如果您设置的ReadTimeout小于服务端处理的时间,程序中会获得一个read timeout异常
         */
        imageSyncScanRequest.setConnectTimeout(3000);
        imageSyncScanRequest.setReadTimeout(10000);
        HttpResponse httpResponse = null;
        try {
            httpResponse = client.doAction(imageSyncScanRequest);
        } catch (Exception e) {
            e.printStackTrace();
        }

        Map<String, String> resultMap = new HashMap<>();

        //服务端接收到请求,并完成处理返回的结果
        if (httpResponse != null && httpResponse.isSuccess()) {
            JSONObject scrResponse = JSON.parseObject(org.apache.commons.codec.binary.StringUtils.newStringUtf8(httpResponse.getHttpContent()));
            System.out.println(JSON.toJSONString(scrResponse, true));
            int requestCode = scrResponse.getIntValue("code");
            //每一张图片的检测结果
            JSONArray taskResults = scrResponse.getJSONArray("data");
            if (200 == requestCode) {
                for (Object taskResult : taskResults) {
                    //单张图片的处理结果
                    int taskCode = ((JSONObject) taskResult).getIntValue("code");
                    //图片要检测的场景的处理结果, 如果是多个场景,则会有每个场景的结果
                    JSONArray sceneResults = ((JSONObject) taskResult).getJSONArray("results");
                    if (200 == taskCode) {
                        for (Object sceneResult : sceneResults) {
                            String scene = ((JSONObject) sceneResult).getString("scene");
                            String label = ((JSONObject) sceneResult).getString("label");
                            String suggestion = ((JSONObject) sceneResult).getString("suggestion");
                            //根据scene和suggetion做相关处理
                            //do something
                            System.out.println("scene = [" + scene + "]");
                            System.out.println("suggestion = [" + suggestion + "]");
                            System.out.println("suggestion = [" + label + "]");
                            if (!suggestion.equals("pass")) {
                                resultMap.put("suggestion", suggestion);
                                resultMap.put("label", label);
                                return resultMap;
                            }
                        }

                    } else {
                        //单张图片处理失败, 原因视具体的情况详细分析
                        log.error("task process fail. task response:" + JSON.toJSONString(taskResult));
                        return null;
                    }
                }
                resultMap.put("suggestion", "pass");
                return resultMap;
            } else {
                /**
                 * 表明请求整体处理失败,原因视具体的情况详细分析
                 */
                log.error("the whole image scan request failed. response:" + JSON.toJSONString(scrResponse));
                return null;
            }
        }
        return null;
    }
}
TanhuaAutoConfiguration
@Bean
@ConditionalOnProperty(prefix = "tanhua.green",value = "enable", havingValue = "true")
public AliyunGreenTemplate aliyunGreenTemplate(GreenProperties properties) {
    return new AliyunGreenTemplate(properties);
}
配置文件
tanhua:
  green:
    enable: true
    accessKeyID: LTAI4GKgob9vZ53k2SZdyAC7
    accessKeySecret: LHLBvXmILRoyw0niRSBuXBZewQ30la
    scenes: porn,terrorism #色情,暴力
单元测试
    @Autowired
    private AliyunGreenTemplate template;

    @Test
    public void test() throws Exception {
//        Long data = analysisMapper.sumAnalysisData("num_registered", "2020-09-14", "2020-09-18");
//        System.out.println(data);
//        Map<String, String> map = template.greenTextScan("本校小额贷款,安全、快捷、方便、无抵押,随机随贷,当天放款,上门服务");
//        map.forEach((k,v)-> System.out.println(k +"--" + v));
        List<String> list = new ArrayList<>();
        list.add("http://images.china.cn/site1000/2018-03/17/dfd4002e-f965-4e7c-9e04-6b72c601d952.jpg");
        Map<String, String> map = template.imageScan(list);
        System.out.println("------------");
        map.forEach((k,v)-> System.out.println(k +"--" + v));
    }

3.2 动态审核

3.2.1 执行流程

为了解决程序间耦合关系,这里采用RabbitMQ + 阿里云完成内容审核

  • 用户发布动态,保存到数据库

  • 发送RabbitMQ消息

  • 管理后台监听消息,对内容(文本、图片审核)

  • 更新动态的状态

在这里插入图片描述

3.2.2 发布消息

    public void publishMovement(Movement movement, MultipartFile[] imageContent) throws IOException {
        //1、判断发布动态的内容是否存在
        if(StringUtils.isEmpty(movement.getTextContent())) {
            throw  new BusinessException(ErrorResult.contentError());
        }
        //2、获取当前登录的用户id
        Long userId = UserHolder.getUserId();
        //3、将文件内容上传到阿里云OSS,获取请求地址
        List<String> medias = new ArrayList<>();
        for (MultipartFile multipartFile : imageContent) {
            String upload = ossTemplate.upload(multipartFile.getOriginalFilename(), multipartFile.getInputStream());
            medias.add(upload);
        }
        //4、将数据封装到Movement对象
        movement.setUserId(userId);
        movement.setMedias(medias);
        //5、调用API完成发布动态
        String movementId = movementApi.publish(movement);
        try {
            amqpTemplate.convertSendAndReceive("tanhua.audit.exchange","audit.movement",movementId);
        }catch (Exception e) {
            e.printStackTrace();
        }
    }

3.2.3 监听器

@Component
public class MovementListener {

    @DubboReference
    private MovementApi movementApi;

    @Autowired
    private AliyunGreenTemplate aliyunGreenTemplate;

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(
                    value = "tanhua.audit.queue",
                    durable = "true"
            ),
            exchange = @Exchange(
                    value = "tanhua.audit.exchange",
                    type = ExchangeTypes.TOPIC),
            key = {"audit.movement"})
    )
    public void listenCreate(String movementId) throws Exception {
        try {
            //1、根据动态id查询动态
            Movement movement = movementApi.findById(movementId);
            //对于RocketMQ消息有可能出现重复,解决方法判断 (幂等性)
            Integer state = 0;
            if(movement != null && movement.getState() == 0) {
                Map<String, String> textScan = aliyunGreenTemplate.greenTextScan(movement.getTextContent());
                Map<String, String> imageScan = aliyunGreenTemplate.imageScan(movement.getMedias());
                if(textScan != null && imageScan != null) {
                    String textSuggestion = textScan.get("suggestion");
                    String imageSuggestion = imageScan.get("suggestion");
                    if ("block".equals(textSuggestion) || "block".equals(textSuggestion)){
                        state = 2;
                    }else if("pass".equals(textSuggestion) || "pass".equals(textSuggestion)) {
                        state = 1;
                    }
                }
            }
            movementApi.update(movementId,state);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3.2.4 movementApi

@Override
public void update(String movementId, Integer state) {
    Query query = Query.query(Criteria.where("id").in(new ObjectId(movementId)));
    Update update = Update.update("state", state);
    mongoTemplate.updateFirst(query,update,Movement.class);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/73563.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用dd+hexdump命令修改环境变量的值和升级uboot

前言 这篇写的较细&#xff0c;使用dd擦除emmc本来就是比较危险的事情&#xff0c;所以一定要细致。哪里没看明白的&#xff0c;赶紧留言问我&#xff0c;可不能存有侥幸心理。 思路大概就是&#xff1a; 1 先从emmc把数据读出来&#xff0c;放一个镜像文件里&#xff0c;使…

【整理】Python全栈技术学习路线

【整理】Python全栈技术学习路线【阶段一】Python基础Linux【阶段二】多任务编程服务器前端基础【阶段三】数据库mini Web框架【阶段四】Dhango框架美多商城项目【阶段五】DRF框架美多商城后台【阶段六】项目部署Flask框架Hm头条【阶段七】人工智能基础推荐系统基础Hm头条推荐系…

带你了解extern “C“

1.extern “C” 这个语法是c的语法。我们知道在一个.c文件中调用另一个.c中实现的函数是没有任何问题的&#xff0c;一个.cpp文件调用另一个.cpp文件中实现的函数也是没有问题的。但是我们如果想要在一个.cpp文件调用另一个.c文件中实现的函数&#xff0c;或者在一个.c文件中调…

双调序列

目录 双调序列 思路: 代码: 时间复杂度: 总结: 题目链接: 双调序列 题目描述&#xff1a; XJ编程小组的童鞋们经常玩一些智力小游戏&#xff0c;某月某日&#xff0c;小朋友们又发明了一种新的序列&#xff1a;双调序列&#xff0c;所谓的双调呢主要是满足如下条件描述…

TensorFlow之分类模型-2

1 基本概念 2 文本分类与情感分析 获取数据集 加载数据集 训练数据集 性能设置 为了提升训练过程中数据处理的性能&#xff0c;keras技术框架提供数据集缓存的功能&#xff0c;使用缓存可以避免读取磁盘数据集时由于IO消耗太多而出现性能瓶颈的问题&#xff0c;如果数据集…

操作系统的主要功能是什么

操作系统的主要功能是进程管理、存储管理、设备管理、文件管理、作业管理。 计算机系统的资源可分为设备资源和信息资源两大类。 操作系统位于底层硬件与用户之间&#xff0c;是两者沟通的桥梁。 1、进程管理&#xff0c;其工作主要是进程调度&#xff0c;在单用户单任务的情…

opcj2-盘点几个常见的Java开源脚手架

很多人抱怨自己是CURDer&#xff0c;很多时候就是在简单的修修改改。如果不书序SSM&#xff08;Spring、SpringMVC和Mybatis&#xff09;套路的人可能开始的时候会感觉非常吃力。但是熟悉之后发现其实就这么回事。SpringMVC负责响应对外接口&#xff0c;Mybatis负责数据库的访问…

TF4-圈子功能

TF4-圈子功能1、首页推荐1.1、接口分析1.2、功能实现1.2.1 controller1.2.2 service1.2.3 API接口1.2.4 请求dto对象2、MongoDB集群3、圈子功能2.1、功能说明1.2、实现方案分析1.3、技术方案(重点)1.4、表结构设计4、圈子实现3.1、环境搭建3.1.1、mongo主键自增3.1.2、实体类Mo…

基于matlab的SVM支持向量机分类仿真,核函数采用RBF函数

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 支持向量机&#xff08;support vector machines, SVM&#xff09;是二分类算法&#xff0c;所谓二分类即把具有多个特性&#xff08;属性&#xff09;的数据分为两类&#xff0c;目前主流机器学…

如何清理 docker 磁盘空间 附讲解(全)

目录前言1. Docker System 命令1.1 docker system df1.2 docker system prune2. 冗余容器或镜像3. 限制容器日志前言 补充docker知识点&#xff0c;可看我之前的文章&#xff1a;Docker零基础从入门到精通&#xff08;全&#xff09; docker 镜像特别容易占空间&#xff0c;稍…

基于Redis实现高性能延时消息队列

最近在倒腾自建博客后端系统&#xff0c;需要用到延时任务的功能&#xff0c;但手头只有一套MySQL和Redis&#xff0c;如果搞一套MQ成本有点大&#xff0c;于是想着用redis实现延时消息队列。有些场景用数据库的定时扫表也能简单实现延时消息的功能&#xff0c;不过对于我这边的…

Bitmap,布隆过滤器初步了解

布隆过滤器使用教程 文章目录布隆过滤器使用教程1.背景2.什么是Bitmap3.布隆过滤器3.1 什么是布隆过滤器3.2 布隆过滤器的作用3.3 布隆过滤器的基本原理4.布隆过滤器的实现Guava和Redisson4.1 实现思路4.2 SpringBoot实现这些操作Bitmap,guava,redisson布隆过滤器1.背景 最近公…

redis知识点汇总

一、Redis的数据类型和数据结构 1、Redis五种数据类型 String&#xff08;字符串&#xff09;、List&#xff08;列表&#xff09;、Hash&#xff08;哈希&#xff09;、Set&#xff08;集合&#xff09;和Sorted Set&#xff08;有序集合&#xff09;。 2、Redis的底层数据…

C# 数据类型分值类型及引用类型

一 程序中的变量与常量 程序的基本任务是&#xff1a;对数据进行处理&#xff1b; 数据分为变量(variable)与常量(literal) int age18; 变量是值可以改变&#xff0c;本质上是内存的空间&#xff0c;用来存储信息 常量的值是固定的&#xff0c;直接写出来的&#xff0c;称字面…

点击按钮,下载文件

实现文件的下载功能 1、使用a标签 直接下载仅适用于浏览器无法识别的文件。 如果是浏览器支持的文件格式&#xff0c;如html、jpg、png、pdf等&#xff0c;则不会触发文件下载&#xff0c;而是直接被浏览器解析并展示 <ahref"http://xxxxxx.rar"download>下载…

vue中的性能优化

文章目录一、Vue为什么要做性能优化二、如何做vue的性能优化1. 网络请求优化link标签项目静态资源压缩懒加载利用浏览器的缓存机制高效复用项目文件总结2. 代码优化3. 用户体验优化场景1场景2一、Vue为什么要做性能优化 性能优化的目的是使网站的加载速度更快&#xff0c;用户…

【语音处理】基于自适应差分脉冲编码调制(ADPCM)的实现研究附Matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步进步&#xff0c;matlab项目目标合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信息&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算…

音视频直播系统之 WebRTC 中的协议UDP、TCP、RTP、RTCP详解

一、UDP/TCP 如果让你自己开发一套实时互动直播系统&#xff0c;在选择网络传输协议时&#xff0c;你会选择使用UDP协议还是TCP协议 假如使用 TCP 会怎样呢&#xff1f;在极端网络情况下&#xff0c;TCP 为了传输的可靠性&#xff0c;将会进行反复重发信息的操作 在 TCP 协议…

Nagios篇之Nagios服务关联飞书实现消息告警

一、前言 通常情况下&#xff0c;我们在利用Nagios监控来做服务器监控时&#xff0c;告警是必不可少的&#xff0c;以便于运维人员能够及时发现异常&#xff0c;进而处理问题&#xff0c;所以关联Nagios就变得极为重要。 Nagios关联告警的形式很多&#xff0c;可以进行短信推送…

wpf-ListView中放置可动态调节范围的刻度尺

需求描述 某个ListView占据整个窗口&#xff0c;当窗口的宽度发生改变时&#xff0c;某一列中显示的、某一行的字符数目&#xff0c;能跟随窗口宽度变化而增减。 下面是我做完的效果&#xff1a;&#xff08;只展示窗口的一部分&#xff09; 此时是窗口缩放的极限&#xff0…