作者 钟来

水产舆情采集海报生成添加公众号数据源

... ... @@ -42,6 +42,28 @@ public class FishAquaticPublicOpinion extends BaseEntity
@ApiModelProperty(value="文件地址")
private String filePath;
@ApiModelProperty(value="舆情类型(1官方公告,2公众号文章,3疑似鱼价,4确认鱼价)")
private Integer aquaticType;
@ApiModelProperty(value="鱼价")
private String fishPrice;
public String getFishPrice() {
return fishPrice;
}
public void setFishPrice(String fishPrice) {
this.fishPrice = fishPrice;
}
public Integer getAquaticType() {
return aquaticType;
}
public void setAquaticType(Integer aquaticType) {
this.aquaticType = aquaticType;
}
public void setId(Integer id)
{
this.id = id;
... ...
... ... @@ -50,6 +50,11 @@
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
</dependency>
<!-- Tesseract OCR Java wrapper -->
<dependency>
<groupId>net.sourceforge.tess4j</groupId>
<artifactId>tess4j</artifactId>
</dependency>
</dependencies>
</project>
\ No newline at end of file
... ...
package com.ruoyi.quartz.task;
import cn.hutool.core.text.UnicodeUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.io.Resources;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.tool.SysLogininforType;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.quartz.task.aquatic.AquaticPublicOpinionBase;
import com.ruoyi.quartz.task.aquatic.AquaticPublicOpinionService;
import com.ruoyi.quartz.task.aquatic.MpWeixinQqCom;
import com.ruoyi.system.domain.sys.SysConfig;
import com.zhonglai.luhui.dao.service.PublicService;
import com.zhonglai.luhui.sys.manager.AsyncManager;
import com.zhonglai.luhui.sys.manager.factory.AsyncFactory;
... ... @@ -17,6 +23,9 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
... ... @@ -39,8 +48,17 @@ public class AquaticPublicOpinionTask {
private List<AquaticPublicOpinionService> aquaticPublicOpinionServices;
@Autowired
protected PublicService publicService;
@Autowired
protected MpWeixinQqCom mpWeixinQqCom;
public void run(String day)
{
//初始微信采集服务
String cookie = publicService.getObject(SysConfig.class,"config_key","task.wx.cookie").getConfigValue();
String token = publicService.getObject(SysConfig.class,"config_key","task.wx.token").getConfigValue();
String user_agent = publicService.getObject(SysConfig.class,"config_key","task.wx.user_agent").getConfigValue();
mpWeixinQqCom.init(cookie,token,user_agent);
for (AquaticPublicOpinionService service : aquaticPublicOpinionServices) {
AsyncManager.me().execute(new TimerTask() {
@Override
... ... @@ -59,11 +77,10 @@ public class AquaticPublicOpinionTask {
public void pushPublicOpinion()
{
List<Map<String,Object>> lsit = publicService.getObjectListBySQL("SELECT * FROM `fish_aquatic_public_opinion` WHERE release_time='"+DateUtils.getDate()+" 00:00:00'");
List<Map<String,Object>> lsit = publicService.getObjectListBySQL("SELECT * FROM `fish_aquatic_public_opinion` WHERE release_time LIKE '"+DateUtils.getDate()+"%' AND aquatic_type<3 and file_path is not null");
if (null != lsit && lsit.size()!=0)
{
List<AbstractMap.SimpleEntry<String, String>> dataList = new ArrayList<>();
for (Map<String,Object> map:lsit)
{
StringBuffer content = new StringBuffer();
... ...
package com.ruoyi.quartz.task.aquatic;
import cn.hutool.http.HttpRequest;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.fish.FishAquaticPublicOpinion;
import net.sourceforge.tess4j.Tesseract;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;
/**
* 微信公众号
* 由于依赖于 Tesseract OCR需要库支持
* ✅ 1. 安装 tesseract 本地依赖
* 如果你使用的是 Ubuntu / Debian:
*
* bash
* 复制
* 编辑
* sudo apt update
* sudo apt install tesseract-ocr libtesseract-dev
* 如果你使用的是 CentOS / RHEL:
*
* bash
* 复制
* 编辑
* sudo yum install tesseract tesseract-langpack-chi
* 2. 配置 Java 运行时加载 .so 的路径
* 启动 Java 应用时加上以下参数(假设你本地 .so 在 /usr/lib/x86_64-linux-gnu):
*
* bash
* 复制
* 编辑
* java -Djava.library.path=/usr/lib/x86_64-linux-gnu -jar yourapp.jar
* 或者设置环境变量:
*
* bash
* 复制
* 编辑
* export LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH
*/
@Service
public class MpWeixinQqCom extends AquaticPublicOpinionBase{
private String cookie = "appmsglist_action_3944220341=card; pgv_pvid=66318384; ptcz=7e9b462221d517b7a86dcdce8a3b70c1e3f14af8c71fa16e5564c73117dad582; ua_id=ucwtv7I222ImpWDCAAAAAOrB9Wbpa8oWXdgbM_8jVz4=; mm_lang=zh_CN; fqm_pvqid=90361cd3-7f8d-432c-8cb6-c238730905d2; RK=FYdMA3xDH9; pac_uid=0_J24szKEP4cMJF; wxuin=37007299026139; gr_user_id=a2b9ab84-edc1-4b61-a352-89b0220df2f8; UM_distinctid=195b6bad08f8fc-0b331424f1b24a-26011d51-1fa400-195b6bad0906ac; ts_uid=6664857577; ptui_loginuin=409188680@qq.com; _qimei_uuid42=197120a272610012082ab8c858de4344b8bff35448; _qimei_fingerprint=2d232406d55bba89a294856e4af0c11f; _qimei_q36=; _qimei_h38=fded4ee3082ab8c858de434402000000c19712; _clck=3944220341|1|fxs|0; uuid=99fb5558abda3ab4bce37a74951caf6b; rand_info=CAESIEnBOOMQHUU0K0zBbCASv5xdvltq+UR6wCnGCcrH+8m4; slave_bizuin=3935963356; data_bizuin=3935963356; bizuin=3935963356; data_ticket=3vm6sCZDGB5wD97JbUiTwJgXCAcs5g06oozVP8x7WFQ1Z5//ACbD4mM/1jA0kMYh; slave_sid=VzdhbmdQZ2IzMGlaczU3YnlDRDQ2TktyN180V0RISjZtbk1HTDVsZUJ5aGZwN0VRa3JwRnVmRVZIR0g2eWJFWnBxTl9Ta1k4NEVoSXc4SnhKaXhyeHp4YkFpcnhjc0xvN1BCekJyZ2dlWUFRb0xENGFsdVNyMFQ0Q1pwU1ZWTUlweEgxUjhlOXVTOGRJRTNq; slave_user=gh_a20cc1ee34e3; xid=0fb05b7b8fee68af4fa1f48af6281f6d; _clsk=9lazsc|1753091231309|1|1|mp.weixin.qq.com/weheat-agent/payload/record";
private String token = "516447166";
private String user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36";
private static Tesseract tesseract = null;
@Value("${task.tesseract-path}")
private String tesseractPath;
private Tesseract getTesseract()
{
if(null == tesseract)
{
tesseract = new Tesseract();
// 放置 traineddata 的文件夹
tesseract.setDatapath(tesseractPath);
// 设置语言
tesseract.setLanguage("chi_sim+eng");
}
return tesseract;
}
public void init(String cookie,String token,String user_agent)
{
this.cookie = cookie;
this.token = token;
this.user_agent = user_agent;
}
@Override
List<FishAquaticPublicOpinion> collect(String day) {
Random rand = new Random();
List<FishAquaticPublicOpinion> list = new ArrayList<>();
String[] gzh = new String[]{"中国水产","水产前沿","淡水渔业","海大集团","海洋与渔业杂志","华中渔业研究社","科学养鱼","南京渔业科技","农民日报","水产养殖编辑部","UCN国际海产资讯","养殖前沿","壹渔业","中国渔业报"};
for (String name: gzh)
{
List<FishAquaticPublicOpinion> publist = getPublishList(day,name,2);
if (null != publist && publist.size()!=0)
{
list.addAll(publist);
}
try {
// 生成一个随机整数,范围是0到99
int randomInt = rand.nextInt(15);
Thread.sleep(randomInt*1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
String[] gzh_yj = new String[]{"海大农牧 水产一线","水产养殖网","农财宝典-大国渔业","大唐洋帆","禾晨科技","鱼市场","喜农和集团"};
for (String name: gzh_yj)
{
List<FishAquaticPublicOpinion> publist = getPublishList(day,name,3);
if (null != publist && publist.size()!=0)
{
list.addAll(publist);
}
try {
// 生成一个随机整数,范围是0到99
int randomInt = rand.nextInt(15);
Thread.sleep(randomInt*1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
return list;
}
@Override
public String getInfo(String info_url) {
try {
Document doc = createDocument(info_url);
StringBuffer text = readtext(doc.getAllElements(),new StringBuffer(),"");
return text.toString();
} catch (IOException e) {
logger.error("解析详情错误:"+info_url,e);
}
return null;
}
private StringBuffer readtext(Elements p,StringBuffer text,String backLine)
{
for (Element element : p) {
if (element.childrenSize()==0)
{
if (element.tagName().equals("img"))
{
String imgUrl = element.absUrl("data-src");
if (imgUrl == null || imgUrl.isEmpty()) {
imgUrl = element.absUrl("src");
if (!imgUrl.isEmpty()) {
String ocrText = doOCR(imgUrl);
if (StringUtils.isNotEmpty(ocrText)) {
text.append("[图片内容>] ").append(ocrText).append("[<图片内容]");
}
}
}
}else {
String line = element.text().trim();
if(StringUtils.isNotEmpty(line) && !line.equals(backLine))
{
text.append(line);
// text.append("\n");
backLine = line;
}
}
}else{
text = readtext(element.children(),text,backLine);
}
}
return text;
}
private String doOCR(String imageUrl) {
logger.info("开始ocr解析:"+imageUrl);
try (InputStream in = new URL(imageUrl).openStream()) {
BufferedImage img = ImageIO.read(in);
if(null == img || img.getWidth()<500|| img.getHeight()<40)
{
return "";
}
logger.info("图片读取成功开始解析:"+img.getWidth()+"X"+img.getHeight());
String text = getTesseract().doOCR(img);
logger.info("结束ocr解析:"+imageUrl);
return text.trim().replaceAll("\\s+", " ");
} catch (Exception e) {
logger.error("OCR错误:"+imageUrl,e);
return "";
}
}
private String getFakeid(String name)
{
String str = null;
try {
str = HttpRequest.get("https://mp.weixin.qq.com/cgi-bin/searchbiz?action=search_biz&begin=0&count=5&query="+ URLEncoder.encode(name, StandardCharsets.UTF_8.toString())+"&token="+token+"&lang=zh_CN&f=json&ajax=1")
.header("User-Agent", user_agent)
.header("Cookie",cookie)
.execute().body();
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e) ;
}
JSONObject jsonObject = JSONObject.parseObject(str);
JSONArray jsonArray = jsonObject.getJSONArray("list");
if(null != jsonArray && jsonArray.size()!=0)
{
return jsonArray.getJSONObject(0).getString("fakeid");
}
logger.error("获取公众号fakeid失败:>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"+name);
return null;
}
private List<FishAquaticPublicOpinion> getPublishList(String day,String name,Integer aquaticType)
{
String fakeid = getFakeid(name);
if (StringUtils.isNotEmpty(fakeid))
{
String str = HttpRequest.get("https://mp.weixin.qq.com/cgi-bin/appmsgpublish?sub=list&search_field=null&begin=0&count=5&query=&fakeid="+ URLEncoder.encode(fakeid)+"&type=101_1&free_publish_type=1&sub_action=list_ex&token="+token+"&lang=zh_CN&f=json&ajax=1")
.header("user-agent", user_agent)
.header("cookie",cookie)
.execute().body();
System.out.println(str);
return parsePublish(day,str,aquaticType);
}
return null;
}
/**
* 解析publish返回的消息
* @param str
*/
public List<FishAquaticPublicOpinion> parsePublish(String day,String str,Integer aquaticType)
{
JSONObject jsonObject = JSONObject.parseObject(str);
System.out.println(str);
JSONObject publish_page = JSONObject.parseObject(jsonObject.getString("publish_page"));
System.out.println(publish_page);
JSONArray jsonArray = publish_page.getJSONArray("publish_list");
if (null != jsonArray && jsonArray.size()!=0)
{
List<FishAquaticPublicOpinion> list = new ArrayList<>();
try {
for (int i = 0; i < jsonArray.size(); i++) {
JSONObject publish = jsonArray.getJSONObject(i);
JSONObject publish_info = JSONObject.parseObject(publish.getString("publish_info"));
System.out.println(publish_info);
JSONArray appmsgex = publish_info.getJSONArray("appmsgex");
System.out.println(appmsgex);
if (null != appmsgex && appmsgex.size() != 0) {
JSONObject appmsg = appmsgex.getJSONObject(0);
String title = appmsg.getString("title");
Integer update_time = appmsg.getInteger("update_time");
Date timeday = new Date(update_time * 1000l);
String time = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, timeday);
if (StringUtils.isNotEmpty(day) && DateUtils.parseDate(time, DateUtils.YYYY_MM_DD).equals(DateUtils.parseDate(day, DateUtils.YYYY_MM_DD))) {
String info_url = appmsg.getString("link");
FishAquaticPublicOpinion aquaticPublicOpinion = new FishAquaticPublicOpinion();
aquaticPublicOpinion.setTitle(title);
aquaticPublicOpinion.setInfoUrl(info_url);
aquaticPublicOpinion.setReleaseTime(timeday);
aquaticPublicOpinion.setCreateTime(new Date());
aquaticPublicOpinion.setAquaticType(aquaticType);
list.add(aquaticPublicOpinion);
} else {
return list;
}
}
}
} catch (Exception e) {
logger.error("数据解析错误:"+str,e);
}
return list;
}
return null;
}
public static void main(String[] args) {
MpWeixinQqCom mpWeixinQqCom = new MpWeixinQqCom();
System.out.println(mpWeixinQqCom.getInfo("https://mp.weixin.qq.com/s/4yygkdF9dnPZc2TUgILKZA"));
// mpWeixinQqCom.getPublishList("2025-07-22","水产养殖网",3);
}
}
... ...
... ... @@ -43,6 +43,7 @@ public class WwwCafsAcCn extends AquaticPublicOpinionBase{
aquaticPublicOpinion.setInfoUrl(url);
aquaticPublicOpinion.setReleaseTime(DateUtils.parseDate(time,"yyyy年MM月dd日"));
aquaticPublicOpinion.setCreateTime(new Date());
aquaticPublicOpinion.setAquaticType(1);
list.add(aquaticPublicOpinion);
}else{
return list;
... ...
... ... @@ -51,6 +51,7 @@ public class WwwChinaCfaOrg extends AquaticPublicOpinionBase{
aquaticPublicOpinion.setInfoUrl(url);
aquaticPublicOpinion.setReleaseTime(DateUtils.parseDate(time,DateUtils.YYYY_MM_DD));
aquaticPublicOpinion.setCreateTime(new Date());
aquaticPublicOpinion.setAquaticType(1);
list.add(aquaticPublicOpinion);
}else{
return list;
... ...
... ... @@ -41,6 +41,7 @@ public class WwwCsfishOrgCn extends AquaticPublicOpinionBase{
aquaticPublicOpinion.setInfoUrl(url);
aquaticPublicOpinion.setReleaseTime(DateUtils.parseDate(time,DateUtils.YYYY_MM_DD));
aquaticPublicOpinion.setCreateTime(new Date());
aquaticPublicOpinion.setAquaticType(1);
list.add(aquaticPublicOpinion);
}else {
return list;
... ...
... ... @@ -92,6 +92,7 @@ public class WwwMoaGovCn extends AquaticPublicOpinionBase{
aquaticPublicOpinion.setInfoUrl(url);
aquaticPublicOpinion.setReleaseTime(DateUtils.parseDate(time,DateUtils.YYYY_MM_DD));
aquaticPublicOpinion.setCreateTime(new Date());
aquaticPublicOpinion.setAquaticType(1);
list.add(aquaticPublicOpinion);
}else {
return list;
... ...
... ... @@ -56,6 +56,7 @@ public class WwwNftecAgriCn extends AquaticPublicOpinionBase{
aquaticPublicOpinion.setInfoUrl(url);
aquaticPublicOpinion.setReleaseTime(DateUtils.parseDate(time,DateUtils.YYYY_MM_DD));
aquaticPublicOpinion.setCreateTime(new Date());
aquaticPublicOpinion.setAquaticType(1);
list.add(aquaticPublicOpinion);
}else {
return list;
... ...
package com.ruoyi.quartz.util;
public class FishPrice {
public String 品种;
public String 地区;
public String 时间;
public String 规格;
public String 价格;
}
\ No newline at end of file
... ...
package com.ruoyi.quartz.util;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.common.utils.DateUtils;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.List;
public class FishPriceTableWithLogo {
public static void main(String[] args) throws Exception {
String json = "[{\"价格\":\"35-36元/斤\",\"品种\":\"鱼仔鳜\",\"地区\":\"广东省\",\"时间\":\"2025-07-22\",\"规格\":\"统货\"},{\"价格\":\"32-33元/斤\",\"品种\":\"饲料鳜\",\"地区\":\"广东省\",\"时间\":\"2025-07-22\",\"规格\":\"统货\"},{\"价格\":\"4元/斤\",\"品种\":\"麦鲮鱼\",\"地区\":\"广东省\",\"时间\":\"2025-07-22\",\"规格\":\"统货\"},{\"价格\":\"36元/斤\",\"品种\":\"鳜鱼\",\"地区\":\"江苏省\",\"时间\":\"2025-07-22\",\"规格\":\"统货\"},{\"价格\":null,\"品种\":\"鳜鱼\",\"地区\":\"安徽省\",\"时间\":\"2025-07-22\",\"规格\":\"统货\"},{\"价格\":\"34元/斤\",\"品种\":\"鳜鱼\",\"地区\":\"湖南省\",\"时间\":\"2025-07-22\",\"规格\":\"统货\"},{\"价格\":\"极36元/斤\",\"品种\":\"鳜鱼\",\"地区\":\"湖北省\",\"时间\":\"2025-07-22\",\"规格\":\"统货\"},{\"价格\":\"17元/斤\",\"品种\":\"加州鲈\",\"地区\":\"广东省佛山市\",\"时间\":\"2025-07-22\",\"规格\":\"8两起\"},{\"价格\":\"23元/斤\",\"品种\":\"加州鲈\",\"地区\":\"四川省成都市\",\"时间\":\"2025-07-22\",\"规格\":\"9两起\"},{\"价格\":\"19元/斤\",\"品种\":\"加州鲈\",\"地区\":\"湖北省\",\"时间\":\"2025-07-22\",\"规格\":\"9两起\"},{\"价格\":\"19元/斤\",\"品种\":\"加州鲈\",\"地区\":\"湖南省华容县\",\"时间\":\"2025-07-22\",\"规格\":\"9两起\"},{\"价格\":\"21-22元/斤\",\"品种\":\"加州鲈\",\"地区\":\"河南省郑州市\",\"时间\":\"2025-07-22\",\"规格\":\"1斤起\"},{\"价格\":\"19.5元/斤\",\"品种\":\"加州鲈\",\"地区\":\"江苏省吴江市\",\"时间\":\"2025-07-22\",\"规格\":\"9两起\"},{\"价格\":\"19.5元/斤\",\"品种\":\"加州鲈\",\"地区\":\"浙江省湖州市\",\"时间\":\"2025-07-22\",\"规格\":\"8两起\"},{\"价格\":\"9.6元/斤\",\"品种\":\"黑鱼\",\"地区\":\"广东省佛山市\",\"时间\":\"2025-07-22\",\"规格\":\"2斤以上5斤头\"},{\"价格\":\"8.8元/斤\",\"品种\":\"黑鱼\",\"地区\":\"广东省佛山市\",\"时间\":\"2025-07-22\",\"规格\":\"4斤头\"},{\"价格\":\"6.9元/斤\",\"品种\":\"黑鱼\",\"地区\":\"广东省佛山市\",\"时间\":\"2025-07-22\",\"规格\":\"1-2斤\"},{\"价格\":\"8元/斤\",\"品种\":\"黑鱼\",\"地区\":\"浙江省湖州市\",\"时间\":\"2025-07-22\",\"规格\":\"3斤以上占3成统货\"},{\"价格\":\"8.8元/斤\",\"品种\":\"黑鱼\",\"地区\":\"广东省\",\"时间\":\"2025-07-22\",\"规格\":\"2斤以上占7成统货\"},{\"价格\":\"11元/斤\",\"品种\":\"黄颡鱼\",\"地区\":\"广东省佛山市\",\"时间\":\"2025-07-22\",\"规格\":\"4两头\"},{\"价格\":\"12元/斤\",\"品种\":\"黄颡鱼\",\"地区\":\"广东省佛山市\",\"时间\":\"2025-07-22\",\"规格\":\"5两头\"},{\"价格\":\"16元/斤\",\"品种\":\"黄颡鱼\",\"地区\":null,\"时间\":\"2025-07-22\",\"规格\":\"4两头\"},{\"价格\":\"11.5元/斤\",\"品种\":\"黄颡鱼\",\"地区\":\"四川省\",\"时间\":\"2025-07-22\",\"规格\":\"5寸起\"},{\"价格\":\"14元/斤\",\"品种\":\"黄颡鱼\",\"地区\":\"浙江省湖州市\",\"时间\":\"2025-07-22\",\"规格\":\"4两头\"},{\"价格\":\"12元/斤\",\"品种\":\"黄颡鱼\",\"地区\":\"湖北省枝江市\",\"时间\":\"2025-07-22\",\"规格\":\"6.5寸\"},{\"价格\":\"9.5-10元/斤\",\"品种\":\"黄颡鱼\",\"地区\":\"湖北省枝江市\",\"时间\":\"2025-07-22\",\"规格\":\"5寸筛\"},{\"价格\":\"11元/斤\",\"品种\":\"黄颡鱼\",\"地区\":\"湖南省常德市\",\"时间\":\"2025-07-22\",\"规格\":\"6寸筛\"},{\"价格\":\"7.4元/斤\",\"品种\":\"鮰鱼\",\"地区\":\"广东省\",\"时间\":\"2025-07-22\",\"规格\":\"1.2斤起\"},{\"价格\":\"8.5元/斤\",\"品种\":\"鮰鱼\",\"地区\":\"江苏省扬州市\",\"时间\":\"2025-07-22\",\"规格\":\"3.2斤起\"},{\"价格\":\"8.7元/斤\",\"品种\":\"鮰鱼\",\"地区\":\"江苏省扬州市\",\"时间\":\"2025-07-22\",\"规格\":\"2.2斤起\"},{\"价格\":\"7.6元/斤\",\"品种\":\"鮰鱼\",\"地区\":\"河南省郑州市\",\"时间\":\"2025-07-22\",\"规格\":\"1.8斤起\"},{\"价格\":\"8.2元/斤\",\"品种\":\"鮰鱼\",\"地区\":\"四川省成都市\",\"时间\":\"2025-07-22\",\"规格\":\"1.2-2.5斤\"}]";
String json2 = "[{\"价格\":\"35-36元/斤\",\"品种\":\"鳜鱼\",\"地区\":\"广东省\",\"时间\":\"2025-07-22\",\"规格\":\"仔鳜\"},{\"价格\":\"32-33元/斤\",\"品种\":\"鳜鱼\",\"地区\":\"广东省\",\"时间\":\"2025-07-22\",\"规格\":\"饲料鳜\"},{\"价格\":\"36元/斤\",\"品种\":\"鳜鱼\",\"地区\":\"江苏省\",\"时间\":null,\"规格\":\"标鱼\"},{\"价格\":\"36元/斤\",\"品种\":\"鳜鱼\",\"地区\":\"安徽省\",\"时间\":\"2025-极速-22\",\"规格\":\"标鱼\"},{\"价格\":\"34元/斤\",\"品种\":\"鳜鱼\",\"地区\":\"湖南省\",\"时间\":\"2025-07-22\",\"规格\":\"标鱼\"},{\"价格\":\"36元/斤\",\"品种\":\"鳜鱼\",\"地区\":\"湖北省\",\"时间\":\"2025-07-22\",\"规格\":\"标鱼\"},{\"价格\":\"17元/斤\",\"品种\":\"加州鲈\",\"地区\":\"广东省佛山市\",\"时间\":\"2025-07-22\",\"规格\":\"8两起\"},{\"价格\":\"23元/斤\",\"品种\":\"加州鲈\",\"地区\":\"四川省成都市\",\"时间\":\"2025-07-22\",\"规格\":\"9两起\"},{\"价格\":\"19元/斤\",\"品种\":\"加州鲈\",\"地区\":\"湖北省\",\"时间\":\"2025-07-22\",\"规格\":\"9两起\"},{\"价格\":\"19元/斤\",\"品种\":\"加州鲈\",\"地区\":\"湖南省华容县\",\"时间\":\"2025-07-22\",\"规格\":\"9两起\"},{\"价格\":\"21-22元/斤\",\"品种\":\"加州鲈\",\"地区\":\"河南省郑州市\",\"时间\":\"2025-07-22\",\"规格\":\"1斤起\"},{\"价格\":\"19.5元/斤\",\"品种\":\"加州鲈\",\"地区\":\"江苏省吴江市\",\"时间\":\"2025-07-22\",\"规格\":\"9两起\"},{\"价格\":\"19.5极速/斤\",\"品种\":\"加州鲈\",\"地区\":\"浙江省湖州市\",\"时间\":\"2025-07-22\",\"规格\":\"8两起\"},{\"价格\":\"9.6元/斤\",\"品种\":\"黑鱼\",\"地区\":\"广东省佛山市\",\"时间\":\"2025-07-22\",\"规格\":\"2斤以上5斤头\"},{\"价格\":\"8.8元/斤\",\"品种\":\"黑鱼\",\"地区\":\"广东省佛山市\",\"时间\":\"2025-07-22\",\"规格\":\"4斤头\"},{\"价格\":\"6.9元/斤\",\"品种\":\"黑鱼\",\"地区\":\"广东省佛山市\",\"时间\":\"2025-07-22\",\"规格\":\"1-2斤中鱼\"},{\"价格\":\"8元/斤\",\"品种\":\"黑鱼\",\"地区\":\"浙江省湖州市\",\"时间\":\"2025-07-22\",\"规格\":\"3斤以上占3成统货\"},{\"价格\":\"8.8元/斤\",\"品种\":\"黑鱼\",\"地区\":\"广东省\",\"时间\":\"2025-07-22\",\"规格\":\"2斤以上占7成统货\"},{\"价格\":\"11元/斤\",\"品种\":\"黄颡鱼\",\"地区\":\"广东省佛山市\",\"时间\":\"2025-07-22\",\"规格\":\"4两头\"},{\"价格\":\"12元/斤\",\"品种\":\"黄颡鱼\",\"地区\":\"广东省佛山市\",\"时间\":\"2025-07-22\",\"规格\":\"5两头\"},{\"价格\":\"16元/斤\",\"品种\":\"黄颡鱼\",\"地区\":\"福建省漳州市\",\"时间\":\"2025-07-22\",\"规格\":\"4两头\"},{\"价格\":\"11.5元/斤\",\"品种\":\"黄颡鱼\",\"地区\":\"四川省\",\"时间\":\"2025-07-22\",\"规格\":\"5寸起\"},{\"价格\":\"14元/斤\",\"品种\":\"黄颡鱼\",\"地区\":\"浙江省湖州市\",\"时间\":\"2025-07-22\",\"规格\":\"4两头\"},{\"价格\":\"12元/斤\",\"品种\":\"黄颡鱼\",\"地区\":\"湖北省枝江市\",\"时间\":\"2025-07-22\",\"规格\":\"6.5寸\"},{\"价格\":\"9.5-10元/斤\",\"品种\":\"黄颡鱼\",\"地区\":\"湖北省枝江市\",\"时间\":\"2025-07-22\",\"规格\":\"5寸筛\"},{\"价格\":\"11元/斤\",\"品种\":\"黄颡鱼\",\"地区\":\"湖南省常德市\",\"时间\":\"2025-07-22\",\"规格\":\"6寸筛\"},{\"价格\":\"7.4元/斤\",\"品种\":\"鮰鱼\",\"地区\":\"广东省\",\"时间\":\"2025-07-22\",\"规格\":\"1.2斤起\"},{\"价格\":\"8.5元/斤\",\"品种\":\"鮰鱼\",\"地区\":\"江苏省扬州市\",\"时间\":\"2025-07-22\",\"规格\":\"3.2斤起\"},{\"价格\":\"8.7元/斤\",\"品种\":\"鮰鱼\",\"地区\":\"江苏省扬州市\",\"时间\":\"2025-07-22\",\"规格\":\"2.2斤起\"},{\"价格\":\"7.6元/斤\",\"品种\":\"鮰鱼\",\"地区\":\"河南省郑州市\",\"时间\":\"2025-07-22\",\"规格\":\"1.8斤起\"},{\"价格\":\"8.2元/斤\",\"品种\":\"鮰鱼\",\"地区\":\"四川省成都市\",\"时间\":\"2025-07-22\",\"规格\":\"1.2-2.5斤\"}]";
// 每个地区维护一个 Set 用于去重
Map<String, Set<String>> dedupMap = new HashMap<>();
// 按地区分组
Map<String, List<FishPrice>> grouped = new LinkedHashMap<>();
addFishPrice(grouped,json,dedupMap);
addFishPrice(grouped,json2,dedupMap);
draw("2025年7月23日",grouped);
}
public static final Font baseFont;
static {
try {
baseFont = Font.createFont(Font.TRUETYPE_FONT,
new File("notosans/NotoSansCJKsc-Medium.otf"));
} catch (FontFormatException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void addFishPrice(Map<String, List<FishPrice>> grouped, String json,Map<String, Set<String>> dedupMap ) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
List<FishPrice> list = mapper.readValue(json, new TypeReference<List<FishPrice>>() {});
for (FishPrice item : list) {
String region = item.地区;
String uniqueKey = (item.品种 + "|" + item.规格 + "|" + item.价格 + "|" + item.时间).trim();
// 初始化分组和去重集合
grouped.computeIfAbsent(region, k -> new ArrayList<>());
dedupMap.computeIfAbsent(region, k -> new HashSet<>());
// 去重:只添加未出现过的记录
if (!dedupMap.get(region).contains(uniqueKey)) {
grouped.get(region).add(item);
dedupMap.get(region).add(uniqueKey);
}
}
}
public static String draw(String day, Map<String, List<FishPrice>> grouped) throws Exception {
// 表格参数
int rowHeight = 30;
int colWidth = 180;
int paddingTop = 140;
int paddingBottom = 120;
int tableWidth = colWidth * 5;
int tableHeight = (grouped.values().stream().mapToInt(List::size).sum() + 1) * rowHeight;
int totalHeight = paddingTop + tableHeight + paddingBottom;
// 创建画布
BufferedImage image = new BufferedImage(tableWidth, totalHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D g = image.createGraphics();
// 背景色
g.setColor(new Color(235, 245, 255)); // 浅蓝
g.fillRect(0, 0, tableWidth, totalHeight);
// 抗锯齿
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 标题
g.setColor(Color.BLACK);
g.setFont(baseFont.deriveFont( Font.BOLD, 28));
drawCenteredString(g, day+" 全国主要鱼价行情表", new Rectangle(0, 20, tableWidth, 40), g.getFont());
// 绘制表格头
g.setFont(baseFont.deriveFont(Font.PLAIN, 16));
g.setColor(Color.BLACK);
String[] headers = {"地区", "品种", "规格", "价格", "时间"};
for (int i = 0; i < headers.length; i++) {
g.drawRect(i * colWidth, paddingTop, colWidth, rowHeight);
g.drawString(headers[i], i * colWidth + 10, paddingTop + 20);
}
// 填充表格数据
int y = paddingTop + rowHeight;
for (Map.Entry<String, List<FishPrice>> entry : grouped.entrySet()) {
String region = entry.getKey();
List<FishPrice> fishes = entry.getValue();
// 地区合并绘制
g.drawRect(0, y, colWidth, fishes.size() * rowHeight);
drawCenteredString(g, region, new Rectangle(0, y, colWidth, fishes.size() * rowHeight), g.getFont());
for (int i = 0; i < fishes.size(); i++) {
FishPrice f = fishes.get(i);
int rowY = y + i * rowHeight;
g.drawRect(colWidth * 1, rowY, colWidth, rowHeight);
g.drawRect(colWidth * 2, rowY, colWidth, rowHeight);
g.drawRect(colWidth * 3, rowY, colWidth, rowHeight);
g.drawRect(colWidth * 4, rowY, colWidth, rowHeight);
g.drawString(null==f.品种?" ":f.品种, colWidth * 1 + 10, rowY + 20);
g.drawString(null==f.规格?" ":f.规格, colWidth * 2 + 10, rowY + 20);
g.drawString(null==f.价格?" ":f.价格, colWidth * 3 + 10, rowY + 20);
g.drawString(safeDate(f.时间), colWidth * 4 + 10, rowY + 20);
}
y += fishes.size() * rowHeight;
}
// 绘制下方 Logo 图片
BufferedImage logoImg = ImageIO.read(new File("logo.png"));
int logoWidth = logoImg.getWidth();
int logoHeight = logoImg.getHeight();
int logoX = (tableWidth - logoWidth) / 2;
int logoY = totalHeight - logoHeight - 40;
g.drawImage(logoImg, logoX, logoY, null);
// 上方文字 Logo
g.setFont(baseFont.deriveFont(Font.ITALIC, 16));
drawCenteredString(g, "由鱼儿乐智慧渔业出品", new Rectangle(logoX, logoY+logoHeight, 60, 20), g.getFont());
g.dispose();
// 输出图片
String saveFile = StyleConfig.savePath + new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()) + "_jiage.png";
ImageIO.write(image, "png", new File(saveFile));
System.out.println("✅ 成功生成图片: "+saveFile);
return saveFile;
}
static void drawCenteredString(Graphics2D g, String text, Rectangle rect, Font font) {
if (null == text)
{
text = "";
}
FontMetrics metrics = g.getFontMetrics(font);
try {
int x = rect.x + (rect.width - metrics.stringWidth(text)) / 2;
int y = rect.y + ((rect.height - metrics.getHeight()) / 2) + metrics.getAscent();
g.setFont(font);
g.drawString(text, x, y);
}catch (Exception e)
{
System.out.println(text);
}
}
static String safeDate(String date) {
if (date == null || !date.matches("\\d{4}-\\d{2}-\\d{2}")) {
return "未知";
}
return date;
}
}
... ...
package com.ruoyi.quartz.util;
import com.google.common.io.Resources;
import com.ruoyi.common.utils.DateUtils;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.AlphaComposite;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.List;
public class MultiTextImageGenerator {
private static final String savePath = "uploadPath/aquaticPublicOpinion/img/";
private static final String backgroundPath = "moban.png";
public static void main(String[] args) throws IOException {
// 1. 示例数据
List<AbstractMap.SimpleEntry<String, String>> dataList = Arrays.asList(
... ... @@ -26,63 +22,111 @@ public class MultiTextImageGenerator {
"期刊集群建设研讨会。中国水产学会主办" +
",70余位代表参会,聚焦期刊建设与智" +
"慧渔业,搭建合作平台,注入新动能,奠" +
"定发展基础。"),
new AbstractMap.SimpleEntry<>("水产期刊集群研讨召开", "2025年7月3日,吉林省长春市举办水产" +
"期刊集群建设研讨会。中国水产学会主办" +
",70余位代表参会,聚焦期刊建设与智" +
"慧渔业,搭建合作平台,注入新动能,奠" +
"定发展基础。"),
new AbstractMap.SimpleEntry<>("水产期刊集群研讨召开", "2025年7月3日,吉林省长春市举办水产" +
"期刊集群建设研讨会。中国水产学会主办" +
",70余位代表参会,聚焦期刊建设与智" +
"慧渔业,搭建合作平台,注入新动能,奠" +
"定发展基础。"),
new AbstractMap.SimpleEntry<>("水产期刊集群研讨召开", "2025年7月3日,吉林省长春市举办水产" +
"期刊集群建设研讨会。中国水产学会主办" +
",70余位代表参会,聚焦期刊建设与智" +
"慧渔业,搭建合作平台,注入新动能,奠" +
"定发展基础。")
);
// 2. 配置加载
generateImage(dataList,new StyleConfig());
}
/**
* 根据传入的数据列表和样式配置生成一张带有背景图的图像。
* 背景图分为顶部 logo、中部纯色、底部 logo 三部分。
* 中间内容区根据数据动态拉伸,高度自动适配。
*/
public static String generateImage(List<AbstractMap.SimpleEntry<String, String>> dataList, StyleConfig style) throws IOException {
// 读取背景图
BufferedImage baseImage = ImageIO.read(new File(style.backgroundPath));
int width = baseImage.getWidth();
int maxTextWidth = width - 2 * style.startX;
// 3. 背景图
BufferedImage image = ImageIO.read(new File(backgroundPath));
int width = image.getWidth();
int height = image.getHeight();
BufferedImage newImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
// 取背景图中部颜色作为中间背景区的纯色背景
int centerY = baseImage.getHeight() / 2;
int centerColorRGB = baseImage.getRGB(width / 2, centerY);
Color pureBackgroundColor = new Color(centerColorRGB);
// 创建临时画布用于计算文字高度
BufferedImage tempImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
Graphics2D tempG = tempImage.createGraphics();
tempG.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
tempG.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
int contentHeight = calculateTotalHeight(dataList, style, tempG, maxTextWidth); // 动态内容高度
tempG.dispose();
// 新图总高度 = 顶部logo + 内容区 + 底部logo
int finalHeight = style.topLogoHeight + contentHeight + style.bottomLogoHeight;
BufferedImage newImage = new BufferedImage(width, finalHeight, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = newImage.createGraphics();
g.drawImage(image, 0, 0, null);
// 抗锯齿
// 绘制顶部 logo 区域
BufferedImage topPart = baseImage.getSubimage(0, 0, width, style.topLogoHeight);
g.drawImage(topPart, 0, 0, null);
// 绘制底部 logo 区域
BufferedImage bottomPart = baseImage.getSubimage(0, baseImage.getHeight() - style.bottomLogoHeight, width, style.bottomLogoHeight);
g.drawImage(bottomPart, 0, finalHeight - style.bottomLogoHeight, null);
// 绘制中部纯色背景(中间拉伸区域)
g.setColor(pureBackgroundColor);
g.fillRect(0, style.topLogoHeight, width, contentHeight);
// 开启抗锯齿和文字抗锯齿
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
int maxTextWidth = width - 2 * style.startX;
int currY = style.startY;
// 内容绘制起始 Y 坐标
int currY = style.topLogoHeight + style.startY;
// 4. 绘制每组内容块
for (int i = 0; i < dataList.size(); i++) {
String title = (i + 1) + ". " + dataList.get(i).getKey();
String content = dataList.get(i).getValue();
String title = (i + 1) + ". " + dataList.get(i).getKey(); // 标题编号 + 标题
String content = dataList.get(i).getValue(); // 内容文本
// 获取标题高度
g.setFont(style.titleFont);
FontMetrics titleMetrics = g.getFontMetrics();
int titleHeight = titleMetrics.getHeight();
// 获取内容文本行
g.setFont(style.contentFont);
FontMetrics contentMetrics = g.getFontMetrics();
List<String> lines = splitTextIntoLines(content, contentMetrics, maxTextWidth);
int lineHeight = contentMetrics.getHeight() + style.lineSpacing;
int contentHeight = lines.size() * lineHeight;
int totalTextHeight = titleHeight + 10 + contentHeight;
int contentBlockHeight = lines.size() * lineHeight;
// 整体文字高度 = 标题 + 间距 + 内容块
int totalTextHeight = titleHeight + 10 + contentBlockHeight;
int blockHeight = totalTextHeight + 2 * style.padding;
int blockWidth = maxTextWidth + 2 * style.padding;
int blockX = style.startX - style.padding;
int blockY = currY;
if (style.showBackgroundBlock)
{
// 绘制圆角背景块(可配置是否显示)
if (style.showBackgroundBlock) {
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, style.backgroundAlpha));
g.setColor(style.backgroundColor);
g.fillRoundRect(blockX, blockY, blockWidth, blockHeight, style.cornerRadius, style.cornerRadius);
}
// 恢复正常透明度,绘制文本内容
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f));
g.setColor(style.textColor);
int textStartY = blockY + style.padding + (blockHeight - 2 * style.padding - totalTextHeight) / 2;
int textStartY = blockY + style.padding;
g.setFont(style.titleFont);
int titleY = textStartY + titleHeight;
g.drawString(title, style.startX, titleY);
... ... @@ -94,24 +138,22 @@ public class MultiTextImageGenerator {
g.drawString(lines.get(j), style.startX, y);
}
currY = blockY + blockHeight + style.blockSpacing;
currY = blockY + blockHeight + style.blockSpacing; // 下一个块起始位置
}
// 5. 添加文字水印
drawTextWatermark(g, width, height, style);
// 6. 添加图片水印
// drawImageWatermark(g, width, height, style);
// 绘制水印文字(右下角)
drawTextWatermark(g, width, finalHeight, style);
g.dispose();
// 7. 保存输出
String saveFile = savePath + DateUtils.getDate()+ "_output.png";
// 生成保存文件名
String saveFile = style.savePath + new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()) + "_xinwen.png";
ImageIO.write(newImage, "png", new File(saveFile));
return saveFile;
}
// 文字自动换行
/**
* 将文本按像素宽度进行自动换行
*/
private static List<String> splitTextIntoLines(String text, FontMetrics metrics, int maxWidth) {
List<String> lines = new ArrayList<>();
StringBuilder line = new StringBuilder();
... ... @@ -129,7 +171,34 @@ public class MultiTextImageGenerator {
return lines;
}
// 文字水印
/**
* 计算总内容区所需高度(含块间距)
*/
private static int calculateTotalHeight(List<AbstractMap.SimpleEntry<String, String>> dataList, StyleConfig style, Graphics2D g, int maxTextWidth) {
int currY = style.startY;
g.setFont(style.titleFont);
FontMetrics titleMetrics = g.getFontMetrics();
int titleHeight = titleMetrics.getHeight();
g.setFont(style.contentFont);
FontMetrics contentMetrics = g.getFontMetrics();
int lineHeight = contentMetrics.getHeight() + style.lineSpacing;
for (AbstractMap.SimpleEntry<String, String> entry : dataList) {
List<String> lines = splitTextIntoLines(entry.getValue(), contentMetrics, maxTextWidth);
int contentHeight = lines.size() * lineHeight;
int totalTextHeight = titleHeight + 10 + contentHeight;
int blockHeight = totalTextHeight + 2 * style.padding;
currY += blockHeight + style.blockSpacing;
}
return currY;
}
/**
* 绘制右下角文字水印
*/
private static void drawTextWatermark(Graphics2D g, int width, int height, StyleConfig style) {
g.setFont(style.watermarkFont);
g.setColor(style.watermarkColor);
... ... @@ -140,26 +209,5 @@ public class MultiTextImageGenerator {
g.drawString(style.watermarkText, x, y);
}
// 图片水印
private static void drawImageWatermark(Graphics2D g, int width, int height, StyleConfig style) {
try {
String watermarkImagePath = style.watermarkImagePath;
File file = new File(watermarkImagePath);
if (!file.exists()) {
System.out.println("⚠️ 水印图片未找到:" + watermarkImagePath);
return;
}
Image logo = ImageIO.read(file).getScaledInstance(
style.watermarkImageWidth,
style.watermarkImageHeight,
Image.SCALE_SMOOTH
);
int x = width - style.watermarkImageWidth - style.watermarkImageMarginX;
int y = height - style.watermarkImageHeight - style.watermarkImageMarginY;
g.drawImage(logo, x, y, null);
} catch (IOException e) {
System.err.println("❌ 加载图片水印失败:" + e.getMessage());
}
}
}
... ...
... ... @@ -7,6 +7,7 @@ import java.io.File;
* 样式配置类
*/
public class StyleConfig {
public static final String savePath = "uploadPath/aquaticPublicOpinion/img/";
public StyleConfig() {
try {
Font baseFont = Font.createFont(Font.TRUETYPE_FONT,
... ... @@ -23,6 +24,9 @@ public class StyleConfig {
contentFont = new Font("Serif", Font.PLAIN, 28);
}
}
public String backgroundPath = "moban.png";
public int topLogoHeight = 245; //顶部保留高度
public int bottomLogoHeight = 150; // 底部保留高度
// 文本样式配置
public Font titleFont = new Font("微软雅黑", Font.BOLD, 36);
... ... @@ -56,7 +60,7 @@ public class StyleConfig {
public int startX = 97;
// 文字区域的起始横坐标(左边距)
public int startY = 245;
public int startY = 0;
// 第一段文字区域的起始纵坐标(上边距)
public int lineSpacing = 5;
... ... @@ -94,5 +98,7 @@ public class StyleConfig {
public int watermarkImageMarginY = 20;
// 图片水印的纵向偏移量(从右下角向内缩进的距离)
}
... ...
... ... @@ -10,8 +10,12 @@ import com.ruoyi.common.utils.GsonConstructor;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.framework.web.domain.Server;
import com.ruoyi.quartz.task.aquatic.MpWeixinQqCom;
import com.ruoyi.quartz.util.FishPrice;
import com.ruoyi.quartz.util.FishPriceTableWithLogo;
import com.ruoyi.quartz.util.MultiTextImageGenerator;
import com.ruoyi.quartz.util.StyleConfig;
import com.ruoyi.system.domain.fish.FishAquaticPublicOpinion;
import com.ruoyi.system.domain.sys.SysMonitorApplication;
import com.ruoyi.system.domain.sys.SysMonitorApplicationLog;
import com.ruoyi.system.domain.sys.SysMonitorServer;
... ... @@ -25,7 +29,7 @@ import com.zhonglai.luhui.admin.qywx.WXBizMsgCrypt;
import com.zhonglai.luhui.admin.service.SendSysMonitorServerMessge;
import com.zhonglai.luhui.dao.service.PublicService;
import com.zhonglai.luhui.device.analysis.comm.util.StringUtils;
import com.zhonglai.luhui.admin.dto.GenerateAquaticPublicOpinionPosterRq;
import com.zhonglai.luhui.sys.manager.AsyncManager;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
... ... @@ -69,6 +73,9 @@ public class ServerController extends BaseController
@Autowired
private ScheduledExecutorService scheduledExecutorService;
@Autowired
private MpWeixinQqCom mpWeixinQqCom;
@ApiOperation("获取详情")
@PreAuthorize("@ss.hasPermi('monitor:server:list')")
@GetMapping()
... ... @@ -184,10 +191,44 @@ public class ServerController extends BaseController
{
day = DateUtils.getDate();
}
List<Map<String,Object>> lsit = publicService.getObjectListBySQL("SELECT * FROM `fish_aquatic_public_opinion` WHERE release_time='"+day+" 00:00:00'");
List<Map<String,Object>> lsit = publicService.getObjectListBySQL("SELECT * FROM `fish_aquatic_public_opinion` WHERE release_time LIKE '"+day+"%' AND aquatic_type<3 and file_path is not null");
return AjaxResult.success(lsit);
}
@ApiOperation("获取当天水产鱼价新闻")
@GetMapping("/getYujiaInfo")
public AjaxResult getYujiaInfo(String day)
{
if(StringUtils.isEmpty(day))
{
day = DateUtils.getDate();
}
List<Map<String,Object>> lsit = publicService.getObjectListBySQL("SELECT * FROM `fish_aquatic_public_opinion` WHERE release_time LIKE '"+day+"%' AND aquatic_type=3 and fish_price is null");
return AjaxResult.success(lsit);
}
@ApiOperation("修改鱼价信息")
@PostMapping("/upYujiaInfo/{id}")
public AjaxResult upYujiaInfo(@PathVariable Integer id,@RequestBody String yujiaInfo)
{
FishAquaticPublicOpinion fishAquaticPublicOpinion = new FishAquaticPublicOpinion();
fishAquaticPublicOpinion.setId(id);
fishAquaticPublicOpinion.setFishPrice(yujiaInfo);
fishAquaticPublicOpinion.setAquaticType(4);
return AjaxResult.success(publicService.updateObject(fishAquaticPublicOpinion,"id"));
}
@ApiOperation("修改鱼价信息为公众号信息")
@PostMapping("/upYujiaInfoToGzh/{id}")
public AjaxResult upYujiaInfoToGzh(@PathVariable Integer id)
{
FishAquaticPublicOpinion fishAquaticPublicOpinion = new FishAquaticPublicOpinion();
fishAquaticPublicOpinion.setId(id);
fishAquaticPublicOpinion.setAquaticType(2);
return AjaxResult.success(publicService.updateObject(fishAquaticPublicOpinion,"id"));
}
@ApiOperation("生成水产新闻海报")
@PostMapping("/generateAquaticPublicOpinionPoster")
public AjaxResult generateAquaticPublicOpinionPoster(@RequestBody String str)
... ... @@ -217,7 +258,38 @@ public class ServerController extends BaseController
}
@ApiOperation("生成当天水产价格海报")
@GetMapping("/generateNowJiagePoster")
public AjaxResult generateNowJiagePoster(String day)
{
if(StringUtils.isEmpty(day))
{
day = DateUtils.getDate();
}
List<Map<String,Object>> list = publicService.getObjectListBySQL("SELECT fish_price FROM `fish_aquatic_public_opinion` WHERE release_time LIKE '"+day+"%' AND aquatic_type=4 and fish_price is not null");
if (null == list || list.size()==0)
{
return AjaxResult.error("没有数据");
}
// 每个地区维护一个 Set 用于去重
Map<String, Set<String>> dedupMap = new HashMap<>();
// 按地区分组
Map<String, List<FishPrice>> grouped = new LinkedHashMap<>();
try {
for (Map<String, Object> item : list)
{
FishPriceTableWithLogo.addFishPrice(grouped, (String) item.get("fish_price"),dedupMap);
}
String savePath = FishPriceTableWithLogo.draw(DateUtils.parseDateToStr("yyyy年MM月dd日",DateUtils.parseDate(day)),grouped);
String imgurl = "https://lh.admin.yu2le.com/api/"+savePath.replace("uploadPath","profile");
return AjaxResult.success(imgurl);
} catch (Exception e) {
logger.error("生成价格海报错误",e);
return AjaxResult.error("生成价格海报错误"+e.getMessage());
}
}
@ApiOperation("获取服务器应用列表")
@GetMapping("/getServiceAoolicationList")
... ... @@ -388,6 +460,31 @@ public class ServerController extends BaseController
return AjaxResult.success(sysMonitorServerLog);
}
@ApiOperation("测试微信公众账号详细采集")
@GetMapping("/mpWeixinQqComGetInfo")
public String mpWeixinQqComGetInfo(String info_url)
{
String str = mpWeixinQqCom.getInfo(info_url);
return str;
}
@ApiOperation("采集微信公众账号详情")
@GetMapping("/updateFishAquaticPublicOpinion")
public AjaxResult updateFishAquaticPublicOpinion(String id)
{
AsyncManager.me().execute(new TimerTask() {
@Override
public void run() {
FishAquaticPublicOpinion aquaticPublicOpinion = publicService.getObject(FishAquaticPublicOpinion.class,"id",id);
Map<String,Object> map = publicService.getObjectList(aquaticPublicOpinion,"id,info_url",null,null,0,0).get(0);
String text = mpWeixinQqCom.getInfo((String) map.get("info_url"));
mpWeixinQqCom.updateSaveInfo(text, (Integer) map.get("id"));
}
});
return AjaxResult.success();
}
public static void main(String[] args) {
String str = "[{\"content\":\"\\n中国水产学会2025年征集水产科普作品(图书、视频、活动),申报截止8月10日,优秀作品将获全国推广。\",\"title\":\"中国水产学会关于开展2025年度优秀水产科普作品征集活动的通知 2025-07-09\"},{\"content\":\"\\n2025年7月3日,长春举办水产期刊集群建设研讨会,70余位代表交流期刊发展及智慧渔业应用,推动行业创新提升。\",\"title\":\"水产期刊集群建设研讨会在长春召开 2025-07-09\"}]";
... ...
# 项目相关配置 jhlt: # 名称 name: zhonglai # 版本 version: 3.8.2 # 版权年份 copyrightYear: 2022 # 实例演示开关 demoEnabled: true # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath) profile: E:/work/idea/Luhui/uploadPath # 获取ip地址开关 addressEnabled: false # 验证码类型 math 数组计算 char 字符验证 captchaType: math # 开发环境配置 server: # 服务器的HTTP端口,默认为8080 port: 8080 servlet: # 应用的访问路径 context-path: / tomcat: # tomcat的URI编码 uri-encoding: UTF-8 # 连接数满后的排队数,默认为100 accept-count: 1000 threads: # tomcat最大线程数,默认为200 max: 800 # Tomcat启动初始化的线程数,默认值10 min-spare: 100 # 日志配置 logging: level: com.ruoyi: debug org.springframework: warn com.zhonglai.luhui: debug # Spring配置 spring: # 资源信息 messages: # 国际化资源文件路径 basename: i18n/messages profiles: active: druid # 文件上传 servlet: multipart: # 单个文件大小 max-file-size: 10MB # 设置总上传的文件大小 max-request-size: 20MB # 服务模块 devtools: restart: # 热部署开关 enabled: true # redis 配置 redis: # 地址 host: 119.23.218.181 # 端口,默认为6379 port: 6379 # 数据库索引 database: 1 # 密码 password: # 连接超时时间 timeout: 10s lettuce: pool: # 连接池中的最小空闲连接 min-idle: 0 # 连接池中的最大空闲连接 max-idle: 8 # 连接池的最大数据库连接数 max-active: 8 # #连接池最大阻塞等待时间(使用负值表示没有限制) max-wait: -1ms # token配置 token: # 令牌自定义标识 header: Authorization # 令牌密钥 secret: abcdefghijklmnopqrstuvwxyz # 令牌有效期(默认30分钟) expireTime: 1440 rediskey: lh-admin # MyBatis配置 mybatis: # 搜索指定包别名 typeAliasesPackage: com.ruoyi.**.domain,com.zhonglai.**.domain # 配置mapper的扫描,找到所有的mapper.xml映射文件 mapperLocations: classpath*:mapper/**/*Mapper.xml # 加载全局的配置文件 configLocation: classpath:mybatis/mybatis-config.xml # PageHelper分页插件 pagehelper: helperDialect: mysql supportMethodsArguments: true params: count=countSql # Swagger配置 swagger: # 是否开启swagger enabled: true # 请求前缀 pathMapping: /dev-api # 防止XSS攻击 xss: # 过滤开关 enabled: true # 排除链接(多个用逗号分隔) excludes: /system/notice # 匹配链接 urlPatterns: /system/*,/monitor/*,/tool/* mqtt: client: device_life: 180 sys: ## // 对于登录login 注册register 验证码captchaImage 允许匿名访问 antMatchers: /login,/register,/captchaImage,/getCacheObject,/v2/api-docs,/tool/gen/generatorCodeFromDb,/data/**,/monitor/server/upload,/monitor/server/getSysMonitorServerList,/monitor/server/getSysMonitorServerLogList,/monitor/server/getNowAquaticPublicOpinion # NameServer地址 rocketmq: name-server: 8.129.224.117:9876 # 默认的消息组 producer: group: deviceCommand send-message-timeout: 30000 send-topic: lh-mqtt-service-deviceCommand-test send-tags: 1
\ No newline at end of file
# 项目相关配置 jhlt: # 名称 name: zhonglai # 版本 version: 3.8.2 # 版权年份 copyrightYear: 2022 # 实例演示开关 demoEnabled: true # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath) profile: E:/work/idea/Luhui/uploadPath # 获取ip地址开关 addressEnabled: false # 验证码类型 math 数组计算 char 字符验证 captchaType: math # 开发环境配置 server: # 服务器的HTTP端口,默认为8080 port: 8080 servlet: # 应用的访问路径 context-path: / tomcat: # tomcat的URI编码 uri-encoding: UTF-8 # 连接数满后的排队数,默认为100 accept-count: 1000 threads: # tomcat最大线程数,默认为200 max: 800 # Tomcat启动初始化的线程数,默认值10 min-spare: 100 # 日志配置 logging: level: com.ruoyi: debug org.springframework: warn com.zhonglai.luhui: debug # Spring配置 spring: # 资源信息 messages: # 国际化资源文件路径 basename: i18n/messages profiles: active: druid # 文件上传 servlet: multipart: # 单个文件大小 max-file-size: 10MB # 设置总上传的文件大小 max-request-size: 20MB # 服务模块 devtools: restart: # 热部署开关 enabled: true # redis 配置 redis: # 地址 host: 119.23.218.181 # 端口,默认为6379 port: 6379 # 数据库索引 database: 1 # 密码 password: # 连接超时时间 timeout: 10s lettuce: pool: # 连接池中的最小空闲连接 min-idle: 0 # 连接池中的最大空闲连接 max-idle: 8 # 连接池的最大数据库连接数 max-active: 8 # #连接池最大阻塞等待时间(使用负值表示没有限制) max-wait: -1ms # token配置 token: # 令牌自定义标识 header: Authorization # 令牌密钥 secret: abcdefghijklmnopqrstuvwxyz # 令牌有效期(默认30分钟) expireTime: 1440 rediskey: lh-admin # MyBatis配置 mybatis: # 搜索指定包别名 typeAliasesPackage: com.ruoyi.**.domain,com.zhonglai.**.domain # 配置mapper的扫描,找到所有的mapper.xml映射文件 mapperLocations: classpath*:mapper/**/*Mapper.xml # 加载全局的配置文件 configLocation: classpath:mybatis/mybatis-config.xml # PageHelper分页插件 pagehelper: helperDialect: mysql supportMethodsArguments: true params: count=countSql # Swagger配置 swagger: # 是否开启swagger enabled: true # 请求前缀 pathMapping: /dev-api # 防止XSS攻击 xss: # 过滤开关 enabled: true # 排除链接(多个用逗号分隔) excludes: /system/notice # 匹配链接 urlPatterns: /system/*,/monitor/*,/tool/* mqtt: client: device_life: 180 sys: ## // 对于登录login 注册register 验证码captchaImage 允许匿名访问 antMatchers: /login,/register,/captchaImage,/getCacheObject,/v2/api-docs,/tool/gen/generatorCodeFromDb,/data/**,/monitor/server/upload,/monitor/server/getSysMonitorServerList,/monitor/server/getSysMonitorServerLogList,/monitor/server/getNowAquaticPublicOpinion # NameServer地址 rocketmq: name-server: 8.129.224.117:9876 # 默认的消息组 producer: group: deviceCommand send-message-timeout: 30000 send-topic: lh-mqtt-service-deviceCommand-test send-tags: 1 task: tesseract-path: E:/work/idea/Luhui/tessdata
\ No newline at end of file
... ...

3.8 KB

... ... @@ -620,6 +620,13 @@
<artifactId>jsoup</artifactId>
<version>1.17.2</version>
</dependency>
<!-- Tesseract OCR Java wrapper -->
<dependency>
<groupId>net.sourceforge.tess4j</groupId>
<artifactId>tess4j</artifactId>
<version>5.4.0</version>
</dependency>
</dependencies>
... ...
不能预览此文件类型
不能预览此文件类型