This commit is contained in:
van
2026-05-09 23:27:52 +08:00
parent 30ff4077fe
commit 7582d22d2a
6 changed files with 183 additions and 86 deletions

View File

@@ -1,12 +1,14 @@
package com.ruoyi.jarvis.domain;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
/**
* TG 黄牛电话库 jarvis_tg_scalper_phone
* TG黄牛电话库(开/慢开前置命中则不走TG一条可含多个 11 位手机号)
*
* @author jarvis
*/
public class TgScalperPhone extends BaseEntity
{
@@ -14,11 +16,13 @@ public class TgScalperPhone extends BaseEntity
private Long id;
/** 11位手机号 */
@Excel(name = "手机号")
private String phone;
/** JSON 数组字符串,如 ["13800138000","13900139000"] */
@Excel(name = "手机号数组")
private String phones;
/** 列表查询条件匹配号码JSON 数组包含精确匹配,或 phones 文本模糊) */
private String phoneQuery;
/** 命中时直接回复企微的备注 */
@Excel(name = "备注")
private String remark;
@@ -26,56 +30,36 @@ public class TgScalperPhone extends BaseEntity
@Excel(name = "状态", readConverterExp = "0=禁用,1=启用")
private Integer status;
public Long getId()
{
return id;
}
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "创建时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
public void setId(Long id)
{
this.id = id;
}
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "更新时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
public String getPhone()
{
return phone;
}
public void setPhone(String phone)
{
this.phone = phone;
}
public String getRemark()
{
return remark;
}
public void setRemark(String remark)
{
this.remark = remark;
}
public Integer getStatus()
{
return status;
}
public void setStatus(Integer status)
{
this.status = status;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getPhones() { return phones; }
public void setPhones(String phones) { this.phones = phones; }
public String getPhoneQuery() { return phoneQuery; }
public void setPhoneQuery(String phoneQuery) { this.phoneQuery = phoneQuery; }
public String getRemark() { return remark; }
public void setRemark(String remark) { this.remark = remark; }
public Integer getStatus() { return status; }
public void setStatus(Integer status) { this.status = status; }
@Override
public Date getCreateTime() { return createTime; }
@Override
public void setCreateTime(Date createTime) { this.createTime = createTime; }
@Override
public Date getUpdateTime() { return updateTime; }
@Override
public void setUpdateTime(Date updateTime) { this.updateTime = updateTime; }
@Override
public String toString()
{
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId())
.append("phone", getPhone())
.append("remark", getRemark())
.append("status", getStatus())
.append("createTime", getCreateTime())
.append("updateTime", getUpdateTime())
.toString();
return "TgScalperPhone{id=" + id + ", phones=" + phones + ", remark=" + remark + ", status=" + status + "}";
}
}

View File

@@ -1,6 +1,7 @@
package com.ruoyi.jarvis.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.ruoyi.jarvis.domain.TgScalperPhone;
/**
@@ -12,8 +13,8 @@ public interface TgScalperPhoneMapper
List<TgScalperPhone> selectTgScalperPhoneList(TgScalperPhone q);
/** 按手机号查(不限状态,供唯一性校验 */
TgScalperPhone selectTgScalperPhoneByPhone(String phone);
/** 某号码是否已被其它行占用JSON 数组含该号 */
Long selectIdHavingPhone(@Param("excludeId") Long excludeId, @Param("cellPhone") String cellPhone);
/** 启用状态下按手机号查(开/慢走前置) */
TgScalperPhone selectEnabledByPhone(String phone);

View File

@@ -1,10 +1,15 @@
package com.ruoyi.jarvis.service.impl;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.jarvis.domain.TgScalperPhone;
import com.ruoyi.jarvis.mapper.TgScalperPhoneMapper;
@@ -37,44 +42,41 @@ public class TgScalperPhoneServiceImpl implements ITgScalperPhoneService
{
return null;
}
return tgScalperPhoneMapper.selectEnabledByPhone(phone.trim());
String p = phone.trim();
if (!MOBILE_11.matcher(p).matches())
{
return null;
}
return tgScalperPhoneMapper.selectEnabledByPhone(p);
}
@Override
public int insertTgScalperPhone(TgScalperPhone row)
{
validatePhone(row.getPhone());
String phonesJson = normalizePhonesJson(row.getPhones());
row.setPhones(phonesJson);
if (!StringUtils.hasText(row.getRemark()))
{
throw new ServiceException("备注不能为空");
}
TgScalperPhone exist = tgScalperPhoneMapper.selectTgScalperPhoneByPhone(row.getPhone().trim());
if (exist != null)
{
throw new ServiceException("手机号已存在");
}
assertNoPhoneConflictAcrossRows(null, phonesJson);
if (row.getStatus() == null)
{
row.setStatus(1);
}
row.setPhone(row.getPhone().trim());
return tgScalperPhoneMapper.insertTgScalperPhone(row);
}
@Override
public int updateTgScalperPhone(TgScalperPhone row)
{
validatePhone(row.getPhone());
String phonesJson = normalizePhonesJson(row.getPhones());
row.setPhones(phonesJson);
if (!StringUtils.hasText(row.getRemark()))
{
throw new ServiceException("备注不能为空");
}
TgScalperPhone other = tgScalperPhoneMapper.selectTgScalperPhoneByPhone(row.getPhone().trim());
if (other != null && !other.getId().equals(row.getId()))
{
throw new ServiceException("手机号已存在");
}
row.setPhone(row.getPhone().trim());
assertNoPhoneConflictAcrossRows(row.getId(), phonesJson);
return tgScalperPhoneMapper.updateTgScalperPhone(row);
}
@@ -90,11 +92,92 @@ public class TgScalperPhoneServiceImpl implements ITgScalperPhoneService
return tgScalperPhoneMapper.deleteTgScalperPhoneById(id);
}
private static void validatePhone(String phone)
private void assertNoPhoneConflictAcrossRows(Long excludeId, String phonesJson)
{
if (!StringUtils.hasText(phone) || !MOBILE_11.matcher(phone.trim()).matches())
for (String cell : parseDistinctPhones(phonesJson))
{
throw new ServiceException("请输入正确的11位手机号");
Long hit = tgScalperPhoneMapper.selectIdHavingPhone(excludeId, cell);
if (hit != null)
{
throw new ServiceException("手机号已在其他记录中出现:" + cell);
}
}
}
/** 接受 JSON 数组字符串或非 JSON 的单个号码,规范为 JSON 数组字符串 */
private static String normalizePhonesJson(String raw)
{
if (!StringUtils.hasText(raw))
{
throw new ServiceException("请至少填写一个11位手机号");
}
String s = raw.trim();
List<String> cells = new ArrayList<>();
if (s.startsWith("["))
{
try
{
JSONArray arr = JSON.parseArray(s);
for (int i = 0; i < arr.size(); i++)
{
Object o = arr.get(i);
if (o != null)
{
cells.add(String.valueOf(o).trim());
}
}
}
catch (Exception e)
{
throw new ServiceException("phones 须为 JSON 数组,如 [\"13800138000\"]");
}
}
else
{
for (String part : s.split("[,;\\s]+"))
{
String t = part.trim();
if (StringUtils.hasText(t))
{
cells.add(t);
}
}
}
Set<String> uniq = new LinkedHashSet<>();
for (String c : cells)
{
if (!MOBILE_11.matcher(c).matches())
{
throw new ServiceException("请输入正确的11位手机号" + c);
}
uniq.add(c);
}
if (uniq.isEmpty())
{
throw new ServiceException("请至少填写一个11位手机号");
}
return JSON.toJSONString(new ArrayList<>(uniq));
}
private static List<String> parseDistinctPhones(String phonesJson)
{
List<String> out = new ArrayList<>();
try
{
JSONArray arr = JSON.parseArray(phonesJson);
for (int i = 0; i < arr.size(); i++)
{
Object o = arr.get(i);
if (o != null)
{
out.add(String.valueOf(o).trim());
}
}
}
catch (Exception e)
{
throw new ServiceException("数据损坏phones 非合法 JSON 数组");
}
return out;
}
}

View File

@@ -6,7 +6,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<resultMap type="TgScalperPhone" id="TgScalperPhoneResult">
<result property="id" column="id" />
<result property="phone" column="phone" />
<result property="phones" column="phones" />
<result property="remark" column="remark" />
<result property="status" column="status" />
<result property="createTime" column="create_time" />
@@ -14,14 +14,19 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap>
<sql id="selectTgScalperPhoneVo">
select id, phone, remark, status, create_time, update_time
select id, phones, remark, status, create_time, update_time
from jarvis_tg_scalper_phone
</sql>
<select id="selectTgScalperPhoneList" parameterType="TgScalperPhone" resultMap="TgScalperPhoneResult">
<include refid="selectTgScalperPhoneVo"/>
<where>
<if test="phone != null and phone != ''"> and phone like concat('%', #{phone}, '%')</if>
<if test="phoneQuery != null and phoneQuery != ''">
and (
JSON_CONTAINS(phones, JSON_QUOTE(#{phoneQuery}), '$')
or phones like concat('%', #{phoneQuery}, '%')
)
</if>
<if test="remark != null and remark != ''"> and remark like concat('%', #{remark}, '%')</if>
<if test="status != null"> and status = #{status}</if>
</where>
@@ -33,28 +38,29 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
where id = #{id}
</select>
<select id="selectTgScalperPhoneByPhone" parameterType="String" resultMap="TgScalperPhoneResult">
<include refid="selectTgScalperPhoneVo"/>
where phone = #{phone}
<select id="selectIdHavingPhone" resultType="java.lang.Long">
select id from jarvis_tg_scalper_phone
where JSON_CONTAINS(phones, JSON_QUOTE(#{cellPhone}), '$')
<if test="excludeId != null">and id != #{excludeId}</if>
limit 1
</select>
<select id="selectEnabledByPhone" parameterType="String" resultMap="TgScalperPhoneResult">
<include refid="selectTgScalperPhoneVo"/>
where phone = #{phone} and status = 1
where status = 1 and JSON_CONTAINS(phones, JSON_QUOTE(#{phone}), '$')
limit 1
</select>
<insert id="insertTgScalperPhone" parameterType="TgScalperPhone" useGeneratedKeys="true" keyProperty="id">
insert into jarvis_tg_scalper_phone
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="phone != null and phone != ''">phone,</if>
<if test="phones != null and phones != ''">phones,</if>
<if test="remark != null">remark,</if>
<if test="status != null">status,</if>
create_time,
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="phone != null and phone != ''">#{phone},</if>
<if test="phones != null and phones != ''">#{phones},</if>
<if test="remark != null">#{remark},</if>
<if test="status != null">#{status},</if>
sysdate(),
@@ -64,7 +70,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<update id="updateTgScalperPhone" parameterType="TgScalperPhone">
update jarvis_tg_scalper_phone
<trim prefix="SET" suffixOverrides=",">
<if test="phone != null and phone != ''">phone = #{phone},</if>
<if test="phones != null and phones != ''">phones = #{phones},</if>
<if test="remark != null">remark = #{remark},</if>
<if test="status != null">status = #{status},</if>
update_time = sysdate(),

View File

@@ -1,19 +1,21 @@
-- TG 管理:黄牛电话库(企微「开」「慢开」命中则直接返回备注,不请求 Telegram
CREATE TABLE IF NOT EXISTS `jarvis_tg_scalper_phone` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`phone` varchar(11) NOT NULL COMMENT '11位手机号',
`phones` json NOT NULL COMMENT '11位手机号JSON数组如 ["13800138000","13900139000"]',
`remark` varchar(2000) NOT NULL COMMENT '命中时直接回复的备注',
`status` tinyint(4) NOT NULL DEFAULT 1 COMMENT '0禁用 1启用',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_phone` (`phone`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='TG黄牛电话库开/慢开前置命中则不走TG';
-- 菜单挂在系统管理下parent_id=2与企微跟踪等同执行后请为角色分配权限并清缓存
-- 若表由旧版(含 phone 单列)创建,请执行 jarvis_tg_scalper_phone_phones_json_upgrade.sql
-- 菜单:挂在「系统管理」下。
-- 标准若依 ry_*.sqlmenu_id=1 为系统管理目录menu_id=2 为「系统监控」目录(勿把 2 当成系统管理)。
INSERT INTO sys_menu VALUES (
2120, 'TG管理-黄牛电话库', 2, 12, 'tgScalperPhone', 'jarvis/tgScalperPhone/index', '', '', 1, 0, 'C', '0', '0',
2120, 'TG管理-黄牛电话库', 1, 12, 'tgScalperPhone', 'jarvis/tgScalperPhone/index', '', '', 1, 0, 'C', '0', '0',
'jarvis:tg:scalperPhone:list', 'phone', 'admin', sysdate(), '', NULL, '开/慢开前置匹配,命中则不请求 Telegram'
);
INSERT INTO sys_menu VALUES (

View File

@@ -0,0 +1,21 @@
-- 已从单列 phone 升级为 phones JSON 数组的库执行此脚本(仅迁移,不含建表)
-- 执行前请备份。
ALTER TABLE `jarvis_tg_scalper_phone`
ADD COLUMN `phones` JSON NULL COMMENT '11位手机号JSON数组' AFTER `id`;
UPDATE `jarvis_tg_scalper_phone`
SET `phones` = JSON_ARRAY(`phone`)
WHERE `phones` IS NULL AND `phone` IS NOT NULL AND `phone` != '';
UPDATE `jarvis_tg_scalper_phone`
SET `phones` = JSON_ARRAY()
WHERE `phones` IS NULL;
-- 若建表时未建唯一索引 uk_phone请跳过本句或改为你的实际索引名
ALTER TABLE `jarvis_tg_scalper_phone` DROP INDEX `uk_phone`;
ALTER TABLE `jarvis_tg_scalper_phone` DROP COLUMN `phone`;
ALTER TABLE `jarvis_tg_scalper_phone`
MODIFY COLUMN `phones` JSON NOT NULL COMMENT '11位手机号JSON数组如 ["13800138000","13900139000"]';