# 第12章 消息推送与内容审核

今日内容介绍

63633303656

学习目标

  • 基于RocketMQ实现消息推送
  • 定时任务完成定时统计
  • 华为云内容审核

1. 基于RocketMQ实现记录日志表

【目标】

掌握RocketMQ实现消息推送

【路径】

1:APP端操作记录分析

2:生产者环境搭建

3:消费者环境搭建

【讲解】

用户在APP端的所有操作都需要记录到后台管理系统数据库中。我们这里采用的是RocketMQ来实现,整体的流程如下

  • 用户在APP完成登录或者注册操作
  • tanhua-server端正常完成业务后,发送MQ消息
  • tanhua-manager端,获取MQ消息体
  • 解析消息,记录日志信息到数据库中

61492681876

1.1. 生产者

1.2.1. 引入依赖

tanhua-server模块pom.xml中

<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-spring-boot-starter</artifactId>
    <version>2.0.3</version>
</dependency>
<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-client</artifactId>
    <version>4.6.0</version>
</dependency>

1.2.2. application.yml

tanhua-server模块application.yml配置rocketMQ服务器相关信息

rocketmq:
  name-server: 192.168.136.160:9876
  producer:
    group: tanhua
    sendMessageTimeout: 10000

1.2.2. UserService

tanhua-server模块UserService的登录方法loginVerification,发送MQ消息。

@Autowired
private RocketMQTemplate rocketMQTemplate;

/**
  * 注册登录-第一步:验证码校验(登录)
  */
public Map<String, Object> loginVerification(String phone, String verificationCode) {
    //消息类型为:用户注册
    String type = "0101";
    ......
    //5用户不存在,自动注册用户
    if (user == null) {
        ......
        //消息类型为:用户登录
     	type = "0202";
    }
    //10.发送消息,记录用户登录注册信息,用于后台统计
    Map<String,Object> message = new HashMap<>();
    message.put("userId",user.getId().toString());
    message.put("type",type);
    message.put("date",new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
    rocketMQTemplate.convertAndSend("tanhua-log",message);
}

1.2.3. 测试

用户登录后,打开RocketMQ管理控制台查看消息是否已经发送成功。

image-20201113210250635

1.2. 消费者

1.2.1. 引入依赖

tanhua-manage模块pom.xml中

<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-spring-boot-starter</artifactId>
    <version>2.0.3</version>
</dependency>
<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-client</artifactId>
    <version>4.6.0</version>
</dependency>

1.2.2. Log

tanhua-manage模块domain包中

package com.tanhua.manage.domain;

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

import java.util.Date;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Log {
    /**
     * id
     */
    private Long id;
    /**
     * 用户id
     */
    private Long userId;
    /**
     * 操作时间
     */
    private String logTime;
    /**
     * 登陆地点
     */
    private String place;
    /**
     * 登陆设备
     */
    private String equipment;
    /**
     * 操作类型,
     * 0101为登录,0102为注册,0201为发动态,0202为浏览动态,0203为动态点赞,0204为动态喜欢,0205为评论,0206为动态取消点赞,0207为动态取消喜欢,0301为发小视频,0302为小视频点赞,0303为小视频取消点赞,0304为小视频评论
     */
    private String type;

    /**
     * 日志创建时间
     */
    private Date created;
}

1.2.3. LogMapper

tanhua-manage模块mapper包中

package com.tanhua.manage.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.tanhua.manage.domain.Log;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface LogMapper extends BaseMapper<Log> {
}

1.2.4. LogService

tanhua-manage模块service包中

package com.tanhua.manage.service;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tanhua.manage.domain.Log;
import com.tanhua.manage.mapper.LogMapper;
import org.springframework.stereotype.Service;

@Service
public class LogService extends ServiceImpl<LogMapper, Log> {
    
}

1.2.5. application.yml

tanhua-manage模块application.yml配置rocketMQ服务器相关信息

rocketmq:
  name-server: 192.168.136.160:9876
#日志配置
logging:
  level:
    root: info
    com:
      tanhua: debug
  pattern:
    console: '%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %msg - %logger{15}%n\'

1.2.6. LogMessageListener

tanhua-manage模块listener包中

package com.tanhua.manage.listener;

import com.alibaba.fastjson.JSON;
import com.tanhua.manage.domain.Log;
import com.tanhua.manage.service.LogService;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.Map;

/**
 * 消费者-日志监听类-往日志表写日志\
 * 1.实现RocketMQListener接口 onMessage方法
 * 2.将监听类放入spring容器@Compent
 * 3.设置@RocketMQMessageListener 监听的主题 消费者组名
 * 4.onMessage方法中业务逻辑编码实现
 */
@Component
@RocketMQMessageListener(
        topic = "tanhua_logs", consumerGroup = "tanhua-log-consumer"
)
@Slf4j
public class LogMessageListener implements RocketMQListener<String> {

    @Autowired
    private LogService logService;

    /**
     * 往日志表写入日志
     * {"userId":"1100","type":"0101","logTime":"2021-01-01"}
     *
     * @param message
     */
    @Override
    public void onMessage(String message) {
        log.debug("*******messsage**********"+message);
        //{"userId":"1100","type":"0101","logTime":"2021-01-01"}
        //1.将message转为map
        Map map = JSON.parseObject(message, Map.class);
        //2.获取每一个数据
        Long userId = Long.parseLong(map.get("userId").toString());
        String type = (String)map.get("type");
        String logTime = (String)map.get("logTime");
        //3.操作数据库日志表
        Log dbLog = new Log();
        dbLog.setUserId(userId);
        dbLog.setLogTime(logTime);
        dbLog.setPlace("中粮");
        dbLog.setEquipment("华为666");
        dbLog.setType(type);
        dbLog.setCreated(new Date());//日志创建时间
        logService.save(dbLog);
        log.debug("*******日志记录成功了**********");

    }
}

【小结】

掌握RocketMQ实现消息推送

2. 定时任务

【目标】

掌握定时统计功能实现

【路径】

1:Spring定时任务入门案例

2:定时数据统计功能实现

【讲解】

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

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

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

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

完成这些功能都需要用到定时任务调度。那究竟何为定时任务调度,一句话概括就是:基于给定的时间点、给定的时间间隔、给定的执行次数自动执行的任务

61493292391

2.1. 入门案例

案例:每隔5秒钟。打印系统时间

2.1.1. ManageApplication

@EnableScheduling:开启定时任务支持

@EnableScheduling
public class ManageApplication {

    public static void main(String[] args) {
        SpringApplication.run(ManageApplication.class,args);
    }
}

2.1.2. AnalysisJob

编写定时执行方法

package com.tanhua.manage.job;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

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

@Component
public class AnalysisJob {

    @Scheduled(cron = "0/5 * * * * ?")
    public void analysis() {
        System.out.println("当前时间:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
    }
}

执行效果:每5秒打印当前系统时间

@Scheduled:配置定时执行的方法

2.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 是一样的。 域之间有空格分隔

*** 星号**

使用星号(*) 指示着你想在这个域上包含所有合法的值。例如,在月份域上使用星号意味着每个月都会触发这个 trigger。
表达式样例: 
0 * 17 * * ? 
意义:每天从下午5点到下午5:59中的每分钟激发一次 trigger。它停在下午 5:59 是因为值 17 在小时域上,在下午 6 点时,小时变为 18 了,也就不再理会这个 trigger,直到下一天的下午5点。 在你希望 trigger 在该域的所有有效值上被激发时使用 * 字符。

? 问号

? 号只能用在日和周域上,但是不能在这两个域上同时使用。你可以认为 ? 字符是 "我并不关心在该域上是什么值。" 这不同于星号,星号是指示着该域上的每一个值。? 是说不为该域指定值。 

不能同时这两个域上指定值的理由是难以解释甚至是难以理解的。基本上,假定同时指定值的话,意义就会变得含混不清了:考虑一下,如果一个表达式在日域上有值11,同时在周域上指定了 WED。那么是要 trigger 仅在每个月的11号,且正好又是星期三那天被激发?还是在每个星期三的11号被激发呢?要去除这种不明确性的办法就是不能同时在这两个域上指定值。 只要记住,假如你为这两域的其中一个指定了值,那就必须在另一个字值上放一个 ?。 

表达式样例: 
0 10,44 14 ? 3 WED 
意义:在三月中的每个星期三的下午 2:10 和 下午 2:44 被触发。

, 逗号

逗号 (,) 是用来在给某个域上指定一个值列表的。例如,使用值 0,15,30,45 在秒域上意味着每15秒触发一个 trigger。 
表达式样例: 
0 0,15,30,45 * * * ? 
意义:每刻钟触发一次 trigger。

/ 斜杠

斜杠 (/) 是用于时间表的递增的。我们刚刚用了逗号来表示每15分钟的递增,但是我们也能写成这样 0/15。
表达式样例: 
0/15 0/30 * * * ? 
意义:在整点和半点时每15秒触发 trigger。

-中划线

中划线 (-) 用于指定一个范围。例如,在小时域上的 3-8 意味着 "3,4,5,6,7 和 8 点。"  域的值不允许回转,所以像 50-10 这样的值是不允许的。 
表达式样例: 
0 45 3-8 ? * * 
意义:在上午的3点至上午的8点的45分时触发 trigger。

L 字母

L 说明了某域上允许的最后一个值。它仅被日和周域支持。当用在日域上,表示的是在月域上指定的月份的最后一天。例如,当月域上指定了 JAN 时,在日域上的 L 会促使 trigger 在1月31号被触发。假如月域上是 SEP,那么 L 会预示着在9月30号触发。换句话说,就是不管指定了哪个月,都是在相应月份的时最后一天触发 trigger。 
表达式 0 0 8 L * ? 意义是在每个月最后一天的上午 8:00 触发 trigger。在月域上的 * 说明是 "每个月"。 
当 L 字母用于周域上,指示着周的最后一天,就是星期六 (或者数字7)。所以如果你需要在每个月的最后一个星期六下午的 11:59 触发 trigger,你可以用这样的表达式 0 59 23 ? * L。 
当使用于周域上,你可以用一个数字与 L 连起来表示月份的最后一个星期 X。例如,表达式 0 0 12 ? * 2L 说的是在每个月的最后一个星期一触发 trigger。 
不要让范围和列表值与 L 连用
虽然你能用星期数(1-7)与 L 连用,但是不允许你用一个范围值和列表值与 L 连用。这会产生不可预知的结果。

W 字母

W 字符代表着*日 (Mon-Fri),并且仅能用于日域中。它用来指定离指定日的最*的一个*日。大部分的商业处理都是基于工作周的,所以 W 字符可能是非常重要的。例如,日域中的 15W 意味着 "离该月15号的最*一个*日。" 假如15号是星期六,那么 trigger 会在14号(星期四)触发,因为距15号最*的是星期一,这个例子中也会是17号(译者Unmi注:不会在17号触发的,如果是15W,可能会是在14号(15号是星期六)或者15号(15号是星期天)触发,也就是只能出现在邻*的一天,如果15号当天为*日直接就会当日执行)。W 只能用在指定的日域为单天,不能是范围或列表值。

#井号

字符仅能用于周域中。它用于指定月份中的第几周的哪一天。例如,如果你指定周域的值为 6#3,它意思是某月的第三个周五 (6=星期五,#3意味着月份中的第三周)。另一个例子 2#1 意思是某月的第一个星期一 (2=星期一,#1意味着月份中的第一周)。注意,假如你指定 #5,然而月份中没有第 5 周,那么该月不会触发。 

2.3. 定时统计

2.3.1. 数据来源分析

统计tb_log表中数据,按照要求存入到tb_analysis_by_day表中

#案例:统计2020-11-14数据
#将tb_log表中的数据,进行统计,记录到tb_analysis_by_day


#新注册用户数
select count(*) from tb_log where log_time ='2021-11-08' and type = '0102'
#活跃用户数
select count(DISTINCT user_id) from tb_log where log_time ='2021-11-08' 

#登陆次数
select count(*) from tb_log where log_time ='2021-11-08' and type = '0101'

#次日留存用户数
select count(DISTINCT user_id) from tb_log where log_time ='2021-11-08' 
and user_id in (select user_id from tb_log where log_time ='2021-11-07' and type = '0102'
)

2.3.2 AnalysisByDay

package com.tanhua.manage.domain;

import com.tanhua.domain.db.BasePojo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class AnalysisByDay extends BasePojo {

    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;

    /**
     * 更新时间
     */
    private Date updated;
}

2.3.3. AnalysisJob

package com.tanhua.manage.job;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.tanhua.manage.domain.AnalysisByDay;
import com.tanhua.manage.mapper.AnalysisByDayMapper;
import com.tanhua.manage.utils.ComputeUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

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

/**
 * 任务类
 */
@Component
@Slf4j
public class AnalysisJob {

    @Autowired
    private AnalysisByDayMapper analysisByDayMapper;

    /**
     * 每隔30分钟将日志表数据统计到概要统计分析表中
     */
    @Scheduled(cron = "* 0/30 * * * ?")
    public void analysis(){
        log.debug("任务运行了***"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        //1 获取当前系统时间 根据时间查询统计分析表 记录是否存在
        DateTime today = DateTime.now();//今天时间
        String todayStr = today.toDateStr();//今天时间字符串
        String yesterday = ComputeUtil.offsetDay(today, -1);
        QueryWrapper<AnalysisByDay> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("record_date",todayStr);
        AnalysisByDay analysisByDay = analysisByDayMapper.selectOne(queryWrapper);
        //2 如果概要统计分析表 记录不存在 保存记录
        Date nowDate = new Date();
        if(analysisByDay == null){
            analysisByDay = new AnalysisByDay();
            analysisByDay.setRecordDate(DateUtil.parse(todayStr));
            //#新注册用户数
            //select count(*) from tb_log where log_time ='2021-11-08' and type = '0102'
            analysisByDay.setNumRegistered(analysisByDayMapper.findNumRegisteredLogin(todayStr,"0102"));//新注册用户数
            //#活跃用户数
            //select count(DISTINCT user_id) from tb_log where log_time ='2021-11-08'
            analysisByDay.setNumActive(analysisByDayMapper.findNumActive(todayStr));//活跃用户数
            //#登陆次数
            //select count(*) from tb_log where log_time ='2021-11-08' and type = '0101'
            analysisByDay.setNumLogin(analysisByDayMapper.findNumRegisteredLogin(todayStr,"0101"));//登陆次数

            //#次日留存用户数
            //select count(DISTINCT user_id) from tb_log where log_time ='2021-11-08'
            //and user_id in (select user_id from tb_log where log_time ='2021-11-07' and type = '0102')
            analysisByDay.setNumRetention1d(analysisByDayMapper.findNumRetention1d(todayStr,yesterday));//次日留存用户数
            analysisByDay.setCreated(nowDate);
            analysisByDay.setUpdated(nowDate);
            analysisByDayMapper.insert(analysisByDay);
        }
        else {
            //3. 如果概要统计分析表 记录存在,则更新
            //#新注册用户数
            //select count(*) from tb_log where log_time ='2021-11-08' and type = '0102'
            analysisByDay.setNumRegistered(analysisByDayMapper.findNumRegisteredLogin(todayStr,"0102"));//新注册用户数
            //#活跃用户数
            //select count(DISTINCT user_id) from tb_log where log_time ='2021-11-08'
            analysisByDay.setNumActive(analysisByDayMapper.findNumActive(todayStr));//活跃用户数
            //#登陆次数
            //select count(*) from tb_log where log_time ='2021-11-08' and type = '0101'
            analysisByDay.setNumLogin(analysisByDayMapper.findNumRegisteredLogin(todayStr,"0101"));//登陆次数

            //#次日留存用户数
            //select count(DISTINCT user_id) from tb_log where log_time ='2021-11-08'
            //and user_id in (select user_id from tb_log where log_time ='2021-11-07' and type = '0102')
            analysisByDay.setNumRetention1d(analysisByDayMapper.findNumRetention1d(todayStr,yesterday));//次日留存用户数
            analysisByDay.setUpdated(nowDate);
            analysisByDayMapper.updateById(analysisByDay);
        }
    }
}

2.3.4. AnalysisByDayMapper

package com.tanhua.manage.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.tanhua.manage.domain.AnalysisByDay;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface AnalysisByDayMapper extends BaseMapper<AnalysisByDay> {
    /**
     *新注册用户数 登陆次数
     * @param todayStr
     * @param type
     * @return
     */
    @Select("select count(*) from tb_log where log_time =#{logTime} and type = #{type} ")
    Integer findNumRegisteredLogin(@Param("logTime") String todayStr,@Param("type")String type);

    /**
     *活跃用户数
     * @param todayStr
     * @return
     */
    @Select("select count(DISTINCT user_id) from tb_log where log_time =#{todayStr}")
    Integer findNumActive(String todayStr);

    /**
     *次日留存用户数
     * @param todayStr
     * @param yesterday
     * @return
     */
    @Select("select count(DISTINCT user_id) from tb_log where log_time =#{todayStr}  " +
            "and user_id in (select user_id from tb_log where log_time =#{yesterday} and type = '0102')")
    Integer findNumRetention1d(@Param("todayStr") String todayStr,@Param("yesterday")  String yesterday);
}

【小结】

掌握定时统计功能实现

3. 华为云内容审核

【目标】

掌握华为云内容审核组件使用

【路径】

1:华为云内容介绍

2:华为云内容组件使用

【讲解】

对于用户发布的动态信息是需要进行审核的,这里我们选择华为云的内容审核服务。

3.1. 介绍

地址:https://www.huaweicloud.com/product/moderation.html

image-20210129165645394

https://www.huaweicloud.com/product/textmoderation.html

image-20210129165811754

我们暂时只需要本和图像审核即可。

3.2. 使用说明

需要先注册华为云账户并且完成实名认证。

快速入门文档

文本审核:https://support.huaweicloud.com/qs-moderation/moderation_07_0001.html

image-20210129170335104

图像审核: https://support.huaweicloud.com/qs-moderation/moderation_07_0002.html

image-20210129170540965

3.2.1. 开通服务

登陆后进入控制台,选择区域为 华东-上海一

image-20210130095554321

image-20210130095701275

image-20210130095801406

3.2.2. API调用信息收集

① domain name

image-20210130101446768

② project name

image-20210130104947033

③ Api基本路径

image-20210130104442482image-20210129171002720

image-20210129171027492

image-20210130105621472

【注意】要根据开通内容审核时选择的区域来选择

这里我们获得调用api的基本路径

Api基本路径
https://moderation.cn-east-3.myhuaweicloud.com

④ 文本与图像审核api获取

image-20210130110555291

文本:POST /v1.0/moderation/text

图像:POST /v1.0/moderation/image

​ 批量:POST /v1.0/moderation/image/batch

文本审核完整api api基本路径+api
POST: https://moderation.cn-east-3.myhuaweicloud.com/v1.0/moderation/text
图像审核完整api api基本路径+api,项目里使用批量,用户发布动态时上传多个图片
POST: https://moderation.cn-east-3.myhuaweicloud.com/v1.0/moderation/image/batch

⑤ Token授权获取

在文本内容审核api获取的上方,点开【如何调用API】->【认证鉴权】

image-20210130112340724

点击【获取用户Token】链接,在弹出的窗口中往下拉,找到 image-20210130112644810

3.4. 抽取组件

tanhua:
  huawei:
    username: hw23848243
    password: aaqq!!
    project: cn-east-3
    domain: hw23848243
    # 图片检测内容 politics:是否涉及政治人物的检测,terrorism:是否包含涉政暴恐元素的检测,porn:是否包含涉黄内容元素的检测,ad:是否包含广告的检测(公测特性),all:包含politics、terrorism和porn三种场景的检测
    categoriesImage: politics,terrorism,porn
    # 文字检测内容 politics:涉政,porn:涉黄,ad:广告,abuse:辱骂,contraband:违禁品,flood:灌水
    categoriesText: politics,porn,ad,abuse,contraband,flood
    textApiUrl: https://moderation.cn-east-3.myhuaweicloud.com/v1.0/moderation/text
    imageApiUrl: https://moderation.cn-east-3.myhuaweicloud.com/v1.0/moderation/image/batch

tanhua-commons模块导入hutool依赖

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.4.3</version>
</dependency>

tanhua父工程中

<!--通用依赖-->
<dependencies>
    。。。。。。
    <dependency>
        <groupId>com.apifan.common</groupId>
        <artifactId>common-random</artifactId>
        <version>1.0.5</version>
    </dependency>
</dependencies>

3.4.1. 引入依赖

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.4.3</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
</dependency>

3.4.2. HuaWeiUGCProperties

package com.tanhua.commons.properties;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConfigurationProperties(prefix = "tanhua.huawei")
@Data
public class HuaWeiUGCProperties {

    private String username;
    private String password;
    private String project;
    private String domain;
    private String categoriesText;
    private String categoriesImage;
    private String textApiUrl;
    private String imageApiUrl;
}

3.4.3. HuaWeiUGCTemplate

package com.tanhua.commons.templates;

import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.tanhua.commons.properties.HuaWeiUGCProperties;
import org.apache.commons.lang3.time.DateUtils;

import java.util.Date;

/**
 * 华为 内容审核 工具模板
 */
public class HuaWeiUGCTemplate {

    private HuaWeiUGCProperties properties;

    private String token;

    private long expire = 0L;

    public HuaWeiUGCTemplate(HuaWeiUGCProperties properties) {
        this.properties = properties;
    }

    /**
     * 文本审核
     * 参考:https://support.huaweicloud.com/api-moderation/moderation_03_0018.html
     * @param textModeration
     * @return
     */
    public boolean textContentCheck(String textModeration) {
        String url = properties.getTextApiUrl();
        String reqBody = JSONUtil.createObj()
            .set("categories", StrUtil.split(properties.getCategoriesText(), ','))
            .set("items", JSONUtil.createArray()
                .set(JSONUtil.createObj()
                    .set("text", textModeration)
                    .set("type", "content")
                )
            ).toString();

        String resBody = HttpRequest.post(url)
            .header("X-Auth-Token", this.getToken())
            .contentType("application/json;charset=utf8")
            .setConnectionTimeout(3000)
            .setReadTimeout(2000)
            .body(reqBody)
            .execute()
            .body();

        JSONObject jsonObject = JSONUtil.parseObj(resBody);
        if (jsonObject.containsKey("result") && jsonObject.getJSONObject("result").containsKey("suggestion")) {
            String suggestion = jsonObject.getJSONObject("result").getStr("suggestion").toUpperCase();
            if ("PASS".equals(suggestion)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 图片审核
     * 参数:https://support.huaweicloud.com/api-moderation/moderation_03_0036.html
     * @param urls 多个图片的完整地址
     * @return
     */
    public boolean imageContentCheck(String[] urls) {
        String url = properties.getImageApiUrl();
        String reqBody = JSONUtil.createObj()
            .set("categories", properties.getCategoriesImage().split(","))
            .set("urls", urls)
            .toString();

        String resBody = HttpRequest.post(url)
            .header("X-Auth-Token", this.getToken())
            .contentType("application/json;charset=utf8")
            .setConnectionTimeout(5000)
            .setReadTimeout(3000)
            .body(reqBody)
            .execute()
            .body();

        System.out.println("resBody=" + resBody);
        JSONObject jsonObject = JSONUtil.parseObj(resBody);
        if (jsonObject.containsKey("result")) {
            //审核结果中如果出现一个block或review,整体结果就是不通过,如果全部为PASS就是通过
            if (StrUtil.contains(resBody, "\"suggestion\":\"block\"")) {
                return false;
            } else if (StrUtil.contains(resBody, "\"suggestion\":\"review\"")) {
                return false;
            } else {
                return true;
            }
        }

        //默认人工审核
        return false;
    }

    /**
     * 获取授权Token
     * 参考 https://support.huaweicloud.com/api-iam/iam_30_0001.html
     * @return
     */
    public synchronized String getToken() {
        // 获取当前系统时间
        Long now = System.currentTimeMillis();
        // 判断token是否超时,超时需要重新获取
        if (now > expire) {
            // token的url
            String url = "https://iam.myhuaweicloud.com/v3/auth/tokens";
            // 构建请求体内容
            String reqBody = JSONUtil.createObj().set("auth", JSONUtil.createObj()
                .set("identity", JSONUtil.createObj()
                    .set("methods", JSONUtil.createArray().set("password"))
                    .set("password", JSONUtil.createObj()
                        .set("user", JSONUtil.createObj()
                            .set("domain", JSONUtil.createObj().set("name", properties.getDomain()))
                            .set("name", properties.getUsername())
                            .set("password", properties.getPassword())
                        )
                    )
                )
                .set("scope", JSONUtil.createObj()
                    .set("project", JSONUtil.createObj()
                        .set("name", properties.getProject())
                    )
                )
            ).toString();
            // 执行请求获取响应结果
            HttpResponse response = HttpRequest.post(url)
                .contentType("application/json;charset=utf8")
                .setConnectionTimeout(3000).setReadTimeout(5000)
                .body(reqBody).execute();
            // 获取返回的token
            token = response.header("X-Subject-Token");
            //设置Token有效时长 避免频繁获取
            setExpireTime(response.body());
        }
        return token;
    }

    /**
     * 设置Token有效时长,如果api有返回,则要提前5分钟获取新的Token
     * 默认有效时长2小时
     * @param jsonString
     */
    private void setExpireTime(String jsonString) {
        try {
            JSONObject jsonObject = JSONUtil.parseObj(jsonString);
            if (jsonObject.containsKey("token") && jsonObject.getJSONObject("token").containsKey("expires_at")) {
                String str = jsonObject.getJSONObject("token").getStr("expires_at");
                str = str.replace("T", " ");
                Date expireAt = DateUtils.parseDate(str.substring(0, 16), "yyyy-MM-dd HH:mm");
                expire = expireAt.getTime()-5*60*1000; // 提前5分钟
            }
        } catch (Exception e) {
        }
        // 没获取到有效期,则1小时后过期
        expire = System.currentTimeMillis() + 60*60*1000;
    }
}

3.4.4. CommonsAutoConfiguration

@Configuration
//自动的读取yml中配置信息,并复制到SmsProperties对象,将此对象存入容器
@EnableConfigurationProperties({
        SmsProperties.class,
        OssProperties.class,
        AipFaceProperties.class,
        HuanXinProperties.class,
        HuaWeiUGCProperties.class
})
public class CommonsAutoConfiguration {
    
    。。。。。。
        
    @Bean
    public HuaWeiUGCTemplate huaWeiUGCTemplate(HuaWeiUGCProperties properties) {
        return new HuaWeiUGCTemplate(properties);
    }
}

3.4.5. application.yml

修改tanhua-manage工程application.yml

tanhua:
  huawei:
    username: 【用户名】
    password: 【密码】    
    project: 【project name】
    domain: 【domain name】
    # 图片检测内容 politics:是否涉及政治人物的检测,terrorism:是否包含涉政暴恐元素的检测,porn:是否包含涉黄内容元素的检测,ad:是否包含广告的检测(公测特性),all:包含politics、terrorism和porn三种场景的检测
    categoriesImage: politics,terrorism,porn
    # 文字检测内容 politics:涉政,porn:涉黄,ad:广告,abuse:辱骂,contraband:违禁品,flood:灌水
    categoriesText: politics,porn,ad,abuse,contraband,flood
    textApiUrl: https://moderation.cn-east-3.myhuaweicloud.com/v1.0/moderation/text
    imageApiUrl: https://moderation.cn-east-3.myhuaweicloud.com/v1.0/moderation/image/batch

3.4.6. 测试

package com.tanhua.manage.test;

import com.tanhua.commons.templates.HuaWeiUGCTemplate;
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;

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

    @Autowired
    private HuaWeiUGCTemplate template;

    @Test
    public void testToken() {
        System.out.println(template.getToken());
    }

    @Test
    public void testText() {
        boolean check = template.textContentCheck("好好先生");
        System.out.println(check);
    }

    @Test
    public void testImages() {
        String[] urls = new String[]{
                "http://tanhua-dev.oss-cn-zhangjiakou.aliyuncs.com/images/logo/9.jpg",
                "http://tanhua-dev.oss-cn-zhangjiakou.aliyuncs.com/images/logo/10.jpg"
        };
        boolean check = template.imageContentCheck(urls);
        System.out.println(check);
    }
}

【小结】

掌握华为云内容审核组件使用

4. 动态审核

【目标】

掌握动态审核功能实现

【路径】

1:了解动态发布审核流程

2:掌握动态审核功能实现

【讲解】

63634133845

61494144188

4.1. quanzi_publish

修改mongodb数据表,添加状态(state)字段,执行命令如下

添加新字段,并设置初始值为未审核状态

db.getCollection('quanzi_publish').update({},{$set:{state:NumberInt(0)}},{multi:1})

4.2. Publish

修改tanhua-domain中修改Publish对象,添加状态state属性

@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(collection = "quanzi_publish")
public class Publish implements java.io.Serializable {

    private static final long serialVersionUID = 8732308321082804771L;
    private ObjectId id; //主键id
    private Long pid; //Long类型,用于推荐系统的模型(自动增长)

    private Long userId;
    private String textContent; //文字
    private List<String> medias; //媒体数据,图片或小视频 url
    private Integer seeType; // 谁可以看,1-公开,2-私密,3-部分可见,4-不给谁看
    private String longitude; //经度
    private String latitude; //纬度
    private String locationName; //位置名称

    private Long created; //发布时间
    private Integer state=0;// 状态0:待审核,1:已审核,2:已驳回
    
    private Integer likeCount=0; //点赞数
    private Integer commentCount=0; //评论数
    private Integer loveCount=0; //喜欢数
}

添加状态属性state

state=0:待审核

state=1:已审核

state=2:已驳回

4.3. MovementsService

修改tanhua-server中的MovementsService类,在添加动态之后,发送MQ消息

   /**
     * 发布动态
     */
    public void savePublish(PublishVo publishVo, MultipartFile[] imageContent) throws IOException {
        Long userId = UserHolder.getUserId();
        // 1.封装PublishVo 用户id
        publishVo.setUserId(userId);//当前用户id
        //2 图片地址
        List<String> medias = new ArrayList<>();
        if(imageContent != null && imageContent.length>0) {
            for (MultipartFile multipartFile : imageContent) {
                String imgUrl = ossTemplate.upload(multipartFile.getOriginalFilename(), multipartFile.getInputStream());
                medias.add(imgUrl);
            }
        }
        publishVo.setMedias(medias);//图片地址
        //3. 调用服务将publishVo传入
        String publishId = publishApi.savePublish(publishVo);

        //4.将动态发布主键id 写入 rocketMQ中
        rocketMQTemplate.convertAndSend("tanhua_publish",publishId);
    }

4.4. PublishApi

修改publishApi中的add方法返回值

    /**
     * 发布动态
     * @param publishVo
     */
    String savePublish(PublishVo publishVo);

    /**
     * 更新动态发布表状态字段
     * @param publishId
     * @param state
     */
    void updatePublish(String publishId, Integer state);

4.5. PublishApiImpl

修改PublishApiImpl中的add方法返回值

    /**
     * 发布动态
     * @param publishVo
     */
    @Override
    public String savePublish(PublishVo publishVo) {
        Long userId = publishVo.getUserId();///当前用户id
        long nowTime = System.currentTimeMillis();
        //1.往发布表插入动态数据
        Publish publish = new Publish();
        BeanUtils.copyProperties(publishVo,publish);//用户id 文本内容 经纬度 图片URL
        publish.setLocationName(publishVo.getLocation());//地址位置
        publish.setId(ObjectId.get());//设置主键id 后续相册表 时间线表 都跟此id 产生关系
        publish.setPid(66l);//推荐系统 忽略此字段即可
        publish.setSeeType(1);//谁可以看,1-公开,2-私密,3-部分可见,4-不给谁看
        publish.setCreated(nowTime);//发布时间
        mongoTemplate.insert(publish);
        //2.往相册表插入我的动态记录
        Album album = new Album();
        album.setId(ObjectId.get());//主键id
        album.setPublishId(publish.getId());//发布id
        album.setCreated(nowTime);//当前时间
        mongoTemplate.insert(album,"quanzi_album_"+userId);
        //3,查询好友表 得到好友ids
        Query query = new Query();
        query.addCriteria(Criteria.where("userId").is(userId));
        List<Friend> friendList = mongoTemplate.find(query, Friend.class);
        //4.根据好友ids往好友时间线表插入数据
        if(!CollectionUtils.isEmpty(friendList)){
            for (Friend friend : friendList) {
                TimeLine timeLine = new TimeLine();
                timeLine.setId(ObjectId.get());//主键id
                timeLine.setUserId(userId);//好友id
                timeLine.setPublishId(publish.getId());//发布id
                timeLine.setCreated(nowTime);//创建时间
                mongoTemplate.insert(timeLine,"quanzi_time_line_"+friend.getFriendId());
            }
        }
        return publish.getId().toHexString();
    }

   /**
     * 更新动态发布表状态字段
     * @param publishId
     * @param state
     */
    @Override
    public void updatePublish(String publishId, Integer state) {
        Query query = new Query();
        query.addCriteria(Criteria.where("id").is(new ObjectId(publishId)));
        Update update = new Update();
        update.set("state",state);
        mongoTemplate.updateFirst(query,update,Publish.class);
    }

4.6. PublishMessageListener

tanhua-manage中添加监听器处理消息

package com.tanhua.manage.listener;

import com.tanhua.commons.templates.HuaWeiUGCTemplate;
import com.tanhua.domain.mongo.Publish;
import com.tanhua.dubbo.api.mongo.PublishApi;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.Reference;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * 动态审核监听类(机器审核)
 */
@Component
@RocketMQMessageListener(
        topic = "tanhua_publish", consumerGroup = "tanhua-publish-consumer"
)
@Slf4j
public class PublishMessageListener implements RocketMQListener<String> {
    
    @Reference
    private PublishApi publishApi;

    @Autowired
    private HuaWeiUGCTemplate huaWeiUGCTemplate;

    /**
     * 动态审核
     * @param publishId
     */
    @Override
    public void onMessage(String publishId) {
        //1根据动态发布id 查询动态对象
        Publish publish = publishApi.findPublish(publishId);
        //2获取出图片地址 文本内容
        List<String> urlList = publish.getMedias(); //图片地址
        String content = publish.getTextContent();//内容
        //3调用华为云组件进行审核
        Integer state = 2;// 状态0:待审核,1:已审核,2:已驳回
        boolean flag1 = huaWeiUGCTemplate.textContentCheck(content);
        boolean flag2 = huaWeiUGCTemplate.imageContentCheck(urlList.toArray(new String[]{}));
        if(flag1 && flag2){
            state = 1;//已经审核通过
        }
        //4更新动态发布表状态字段
        publishApi.updatePublish(publishId,state);
    }
}

【小结】

掌握动态审核功能实现

总结

  • 基于RocketMQ完成操作日志的收集
  • 后台管理系统
    • 手机端操作完成之后,tanhua-server向rocketMQ发送操作日志消息
    • 后台系统tanhua-manage从RocketMQ获取消息,写入到后台系统的日表中 tb_log
    • 通过定时任务,完成数据的统计和分析,写入到统计表中
    • 从统计表中,查询统计结果
  • 华为云内容审核
  • 动态审核
    • 整体执行流程
posted on 2022-04-23 15:36  ofanimon  阅读(186)  评论(0编辑  收藏  举报
// 侧边栏目录 // https://blog-static.cnblogs.com/files/douzujun/marvin.nav.my1502.css