no message

This commit is contained in:
cb 2025-12-23 10:53:19 +08:00
parent a6c588d411
commit 57bc8ab4c9
13 changed files with 470 additions and 2 deletions

View File

@ -14,6 +14,10 @@ import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.system.mapper.MaterialStatsDailyMapper;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
/** /**
* 素材Controller * 素材Controller
@ -30,6 +34,9 @@ public class ClewMaterialController extends BaseController
@Autowired @Autowired
private IClewMaterialService clewMaterialService; private IClewMaterialService clewMaterialService;
@Autowired
private MaterialStatsDailyMapper materialStatsDailyMapper;
@RequiresPermissions("system:material:view") @RequiresPermissions("system:material:view")
@GetMapping() @GetMapping()
public String material() public String material()
@ -138,9 +145,16 @@ public class ClewMaterialController extends BaseController
*/ */
@PostMapping("/app/click/{id}") @PostMapping("/app/click/{id}")
@ResponseBody @ResponseBody
public AjaxResult incrementClickCount(@PathVariable("id") Long id) public AjaxResult incrementClickCount(@PathVariable("id") Long id, HttpServletRequest request)
{ {
System.out.println("点击统计接口被调用素材ID: " + id); System.out.println("点击统计接口被调用素材ID: " + id);
String appName = "黑猫搞定逾期";
String statDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
ClewMaterial material = clewMaterialService.selectClewMaterialById(id);
String title = material != null ? material.getTitle() : null;
String labels = material != null ? material.getLabels() : null;
String appSource = material != null && material.getSourceApp() != null ? String.valueOf(material.getSourceApp()) : "unknown";
materialStatsDailyMapper.incrClick(id, appName, appSource, statDate, title, labels);
int result = clewMaterialService.incrementClickCount(id); int result = clewMaterialService.incrementClickCount(id);
System.out.println("点击统计结果: " + result); System.out.println("点击统计结果: " + result);
return toAjax(result); return toAjax(result);
@ -151,11 +165,22 @@ public class ClewMaterialController extends BaseController
*/ */
@PostMapping("/app/submit/{id}") @PostMapping("/app/submit/{id}")
@ResponseBody @ResponseBody
public AjaxResult incrementSubmitCount(@PathVariable("id") Long id) public AjaxResult incrementSubmitCount(@PathVariable("id") Long id, HttpServletRequest request)
{ {
System.out.println("提交统计接口被调用素材ID: " + id); System.out.println("提交统计接口被调用素材ID: " + id);
String appName = "黑猫搞定逾期";
String statDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
ClewMaterial material = clewMaterialService.selectClewMaterialById(id);
String title = material != null ? material.getTitle() : null;
String labels = material != null ? material.getLabels() : null;
String appSource = material != null && material.getSourceApp() != null ? String.valueOf(material.getSourceApp()) : "unknown";
materialStatsDailyMapper.incrSubmit(id, appName, appSource, statDate, title, labels);
int result = clewMaterialService.incrementSubmitCount(id); int result = clewMaterialService.incrementSubmitCount(id);
System.out.println("提交统计结果: " + result); System.out.println("提交统计结果: " + result);
return toAjax(result); return toAjax(result);
} }
private String defaultString(String val, String def) {
return (val == null || val.trim().isEmpty()) ? def : val.trim();
}
} }

View File

@ -0,0 +1,48 @@
package com.ruoyi.web.controller.system;
import java.util.List;
import com.ruoyi.system.domain.MaterialStatsDaily;
import com.ruoyi.system.service.IMaterialStatsDailyService;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/system/material/stats")
public class MaterialStatsDailyController extends BaseController
{
@Autowired
private IMaterialStatsDailyService statsService;
/**
* 分页查询素材转化统计
*/
@PostMapping("/list")
public TableDataInfo list(@RequestParam(value = "appName", required = false) String appName,
@RequestParam(value = "appSource", required = false) String appSource,
@RequestParam(value = "startDate", required = false) String startDate,
@RequestParam(value = "endDate", required = false) String endDate)
{
startPage();
List<MaterialStatsDaily> list = statsService.selectStats(appName, appSource, startDate, endDate);
for (MaterialStatsDaily item : list) {
item.setAppSource(mapSource(item.getAppSource()));
}
return getDataTable(list);
}
private String mapSource(String code) {
if (code == null || code.trim().isEmpty()) return "未知";
switch (code.trim()) {
case "1": return "OPPO";
case "2": return "vivo";
case "3": return "华为";
case "4": return "小米";
case "5": return "应用宝";
case "6": return "百度";
default: return code;
}
}
}

View File

@ -0,0 +1,16 @@
package com.ruoyi.web.controller.system;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/system/material")
public class MaterialStatsDailyViewController {
@GetMapping("/statsPage")
public String statsPage() {
return "system/material/stats";
}
}

View File

@ -0,0 +1,82 @@
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<th:block th:include="include :: header('素材转化统计')" />
<link rel="stylesheet" href="/css/bootstrap.min.css" />
</head>
<body class="gray-bg">
<div class="container-div">
<div class="row">
<div class="col-sm-12 search-collapse">
<form id="formId">
<div class="select-list">
<ul>
<li>
<label>应用:</label>
<select name="appName">
<option value="">全部</option>
<option value="黑猫搞定逾期">黑猫搞定逾期</option>
<option value="大象债务处理">大象债务处理</option>
</select>
</li>
<li>
<label>来源:</label>
<select name="appSource">
<option value="">全部</option>
<option value="1">OPPO</option>
<option value="2">vivo</option>
<option value="3">华为</option>
<option value="4">小米</option>
<option value="5">应用宝</option>
<option value="6">百度</option>
</select>
</li>
<li>
<label>开始日期:</label>
<input type="date" name="startDate"/>
</li>
<li>
<label>结束日期:</label>
<input type="date" name="endDate"/>
</li>
<li>
<a class="btn btn-primary btn-rounded btn-sm" onclick="$.table.search()"><i class="fa fa-search"></i>&nbsp;搜索</a>
<a class="btn btn-warning btn-rounded btn-sm" onclick="$.form.reset()"><i class="fa fa-refresh"></i>&nbsp;重置</a>
</li>
</ul>
</div>
</form>
</div>
<div class="col-sm-12 select-table table-striped">
<table id="bootstrap-table"></table>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div id="summary" style="padding:10px"></div>
</div>
</div>
</div>
<th:block th:include="include :: footer" />
<script th:inline="javascript">
var prefix = ctx + "system/material/stats";
$(function() {
var options = {
url: prefix + "/list",
modalName: "素材转化统计",
columns: [
{ field: 'statDate', title: '日期' },
{ field: 'appName', title: '应用' },
{ field: 'appSource', title: '来源' },
{ field: 'title', title: '标题' },
{ field: 'labels', title: '标签' },
{ field: 'clickCnt', title: '点击数' },
{ field: 'submitCnt', title: '提交数' },
{ field: 'conversionRate', title: '转化率(%)' }
]
};
$.table.init(options);
});
</script>
</body>
</html>

View File

@ -0,0 +1,100 @@
package com.ruoyi.system.domain;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;
import java.util.Date;
public class MaterialStatsDaily extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 主键 */
private Long id;
/** 素材ID */
@Excel(name = "素材ID")
private Long materialId;
/** 应用名称 */
@Excel(name = "应用")
private String appName;
/** APP来源 */
@Excel(name = "APP来源")
private String appSource;
/** 统计日期 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Excel(name = "统计日期", width = 30, dateFormat = "yyyy-MM-dd")
private Date statDate;
/** 素材标题 */
@Excel(name = "素材标题")
private String title;
/** 素材标签 */
@Excel(name = "素材标签")
private String labels;
/** 点击数 */
@Excel(name = "点击数")
private Integer clickCnt;
/** 提交数 */
@Excel(name = "提交数")
private Integer submitCnt;
/** 转化率 */
@Excel(name = "转化率(%)")
private Double conversionRate;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public Long getMaterialId() { return materialId; }
public void setMaterialId(Long materialId) { this.materialId = materialId; }
public String getAppName() { return appName; }
public void setAppName(String appName) { this.appName = appName; }
public String getAppSource() { return appSource; }
public void setAppSource(String appSource) { this.appSource = appSource; }
public Date getStatDate() { return statDate; }
public void setStatDate(Date statDate) { this.statDate = statDate; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getLabels() { return labels; }
public void setLabels(String labels) { this.labels = labels; }
public Integer getClickCnt() { return clickCnt; }
public void setClickCnt(Integer clickCnt) { this.clickCnt = clickCnt; }
public Integer getSubmitCnt() { return submitCnt; }
public void setSubmitCnt(Integer submitCnt) { this.submitCnt = submitCnt; }
public Double getConversionRate() { return conversionRate; }
public void setConversionRate(Double conversionRate) { this.conversionRate = conversionRate; }
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId())
.append("materialId", getMaterialId())
.append("appName", getAppName())
.append("appSource", getAppSource())
.append("statDate", getStatDate())
.append("title", getTitle())
.append("labels", getLabels())
.append("clickCnt", getClickCnt())
.append("submitCnt", getSubmitCnt())
.append("conversionRate", getConversionRate())
.append("createTime", getCreateTime())
.toString();
}
}

View File

@ -0,0 +1,41 @@
package com.ruoyi.system.mapper;
import java.util.List;
import com.ruoyi.system.domain.MaterialStatsDaily;
import org.apache.ibatis.annotations.Param;
public interface MaterialStatsDailyMapper
{
/**
* 插入或更新日统计数据MySQL 唯一键冲突时更新
*/
int insertOrUpdate(MaterialStatsDaily record);
/**
* 按条件查询统计列表
*/
List<MaterialStatsDaily> selectList(@Param("appName") String appName,
@Param("appSource") String appSource,
@Param("startDate") String startDate,
@Param("endDate") String endDate);
/**
* 点击数+1存在即更新不存在则插入 0,1,0
*/
int incrClick(@Param("materialId") Long materialId,
@Param("appName") String appName,
@Param("appSource") String appSource,
@Param("statDate") String statDate,
@Param("title") String title,
@Param("labels") String labels);
/**
* 提交数+1存在即更新不存在则插入 1,0,0
*/
int incrSubmit(@Param("materialId") Long materialId,
@Param("appName") String appName,
@Param("appSource") String appSource,
@Param("statDate") String statDate,
@Param("title") String title,
@Param("labels") String labels);
}

View File

@ -0,0 +1,12 @@
package com.ruoyi.system.service;
import java.util.List;
import com.ruoyi.system.domain.MaterialStatsDaily;
public interface IMaterialStatsDailyService
{
/**
* 按条件查询统计列表
*/
List<MaterialStatsDaily> selectStats(String appName, String appSource, String startDate, String endDate);
}

View File

@ -0,0 +1,21 @@
package com.ruoyi.system.service.impl;
import java.util.List;
import com.ruoyi.system.domain.MaterialStatsDaily;
import com.ruoyi.system.mapper.MaterialStatsDailyMapper;
import com.ruoyi.system.service.IMaterialStatsDailyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class MaterialStatsDailyServiceImpl implements IMaterialStatsDailyService
{
@Autowired
private MaterialStatsDailyMapper statsMapper;
@Override
public List<MaterialStatsDaily> selectStats(String appName, String appSource, String startDate, String endDate)
{
return statsMapper.selectList(appName, appSource, startDate, endDate);
}
}

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.system.mapper.MaterialStatsDailyMapper">
<!-- 插入或更新日统计数据(已废弃,仅保留兼容,改为简单插入) -->
<insert id="insertOrUpdate" parameterType="com.ruoyi.system.domain.MaterialStatsDaily">
INSERT INTO material_stats_daily
(material_id, app_name, app_source, stat_date, title, labels, click_cnt, submit_cnt, conversion_rate)
VALUES
(#{materialId}, #{appName}, #{appSource}, #{statDate}, #{title}, #{labels}, #{clickCnt}, #{submitCnt}, #{conversionRate})
</insert>
<!-- 按条件查询统计列表(区间聚合) -->
<select id="selectList" resultType="com.ruoyi.system.domain.MaterialStatsDaily">
SELECT
stat_date AS statDate,
app_name AS appName,
app_source AS appSource,
title,
labels,
SUM(click_cnt) AS clickCnt,
SUM(submit_cnt) AS submitCnt,
CASE WHEN SUM(click_cnt) > 0 THEN ROUND(SUM(submit_cnt) * 100.0 / SUM(click_cnt), 2) ELSE 0.00 END AS conversionRate
FROM material_stats_daily
<where>
<if test="appName != null and appName != ''"> AND app_name = #{appName} </if>
<if test="appSource != null and appSource != ''"> AND app_source = #{appSource} </if>
<if test="startDate != null and startDate != ''"> AND stat_date &gt;= #{startDate} </if>
<if test="endDate != null and endDate != ''"> AND stat_date &lt;= #{endDate} </if>
</where>
GROUP BY stat_date, app_name, app_source, title, labels
ORDER BY stat_date DESC
</select>
<!-- 点击事件:每次插入一条记录 -->
<insert id="incrClick">
INSERT INTO material_stats_daily
(material_id, app_name, app_source, event_type, stat_date, event_time, title, labels, click_cnt, submit_cnt, conversion_rate)
VALUES
(#{materialId}, #{appName}, #{appSource}, 'click', #{statDate}, NOW(), #{title}, #{labels}, 1, 0, 0.00)
</insert>
<!-- 提交事件:每次插入一条记录 -->
<insert id="incrSubmit">
INSERT INTO material_stats_daily
(material_id, app_name, app_source, event_type, stat_date, event_time, title, labels, click_cnt, submit_cnt, conversion_rate)
VALUES
(#{materialId}, #{appName}, #{appSource}, 'submit', #{statDate}, NOW(), #{title}, #{labels}, 0, 1, 0.00)
</insert>
</mapper>

View File

@ -0,0 +1,11 @@
ALTER TABLE `material_stats_daily`
DROP INDEX `uk_material_day`;
ALTER TABLE `material_stats_daily`
ADD COLUMN `event_type` varchar(16) NOT NULL COMMENT '事件类型: click/submit' AFTER `app_source`,
ADD COLUMN `event_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '事件时间' AFTER `stat_date`;
-- 说明:
-- 1. 取消唯一键,允许同一天多条记录
-- 2. 每次点击/提交都插入一条事件click_cnt/submit_cnt分布为(1,0)或(0,1)
-- 3. 区间统计按SUM(click_cnt)/SUM(submit_cnt)计算转化率

View File

@ -0,0 +1,2 @@
-- 在clew_material表中增加点击数字段
ALTER TABLE clew_material ADD COLUMN click_count INT DEFAULT 0 COMMENT '点击次数' AFTER apply_num;

View File

@ -0,0 +1,4 @@
-- 在clew_material表中增加提交次数和有效率字段
ALTER TABLE clew_material
ADD COLUMN submit_count INT DEFAULT 0 COMMENT '提交次数' AFTER click_count,
ADD COLUMN efficiency_rate DECIMAL(5,2) DEFAULT 0.00 COMMENT '有效率(提交次数/点击次数)' AFTER submit_count;

52
test_manual_service.md Normal file
View File

@ -0,0 +1,52 @@
# 转人工服务功能测试说明
## 修复内容
### 1. 修复session_id字段缺失问题
- **问题**: 在acceptManualRequest和rejectManualRequest方法中创建ManualServiceSessions对象时缺少manualSessionId字段
- **修复**: 将setUserId(requestId)改为setManualSessionId(requestId)
- **影响**: 确保更新操作能正确定位到要修改的记录
### 2. 修复status字段数据截断问题
- **问题**: status字段使用长字符串值("PENDING", "ACCEPTED", "REJECTED")导致数据截断
- **修复**: 统一使用单字符状态码
- "0" - 待处理 (原PENDING)
- "1" - 已接受 (原ACCEPTED)
- "2" - 已拒绝 (原REJECTED)
- **影响**: 避免"Data truncated for column 'status'"错误
## 测试步骤
### 1. 创建转人工请求
```
POST /customer/transfer
参数: sessionId=123, reason="需要人工协助"
预期: 成功创建请求status=0
```
### 2. 接受转人工请求
```
POST /customer/manual-requests/{requestId}/accept
预期: 成功更新状态为1设置serviceId
```
### 3. 拒绝转人工请求
```
POST /customer/manual-requests/{requestId}/reject
预期: 成功更新状态为2设置serviceId
```
## 验证要点
1. 插入操作不再报"Field 'session_id' doesn't have a default value"错误
2. 更新操作不再报"Data truncated for column 'status'"错误
3. 状态值正确保存和查询
4. 主键字段正确设置,更新操作能找到对应记录
## 状态码对照表
| 数字码 | 含义 | 原字符串值 |
|--------|------|------------|
| 0 | 待处理 | PENDING |
| 1 | 已接受 | ACCEPTED |
| 2 | 已拒绝 | REJECTED |