作者 钟来

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

@@ -42,6 +42,28 @@ public class FishAquaticPublicOpinion extends BaseEntity @@ -42,6 +42,28 @@ public class FishAquaticPublicOpinion extends BaseEntity
42 @ApiModelProperty(value="文件地址") 42 @ApiModelProperty(value="文件地址")
43 private String filePath; 43 private String filePath;
44 44
  45 + @ApiModelProperty(value="舆情类型(1官方公告,2公众号文章,3疑似鱼价,4确认鱼价)")
  46 + private Integer aquaticType;
  47 +
  48 + @ApiModelProperty(value="鱼价")
  49 + private String fishPrice;
  50 +
  51 + public String getFishPrice() {
  52 + return fishPrice;
  53 + }
  54 +
  55 + public void setFishPrice(String fishPrice) {
  56 + this.fishPrice = fishPrice;
  57 + }
  58 +
  59 + public Integer getAquaticType() {
  60 + return aquaticType;
  61 + }
  62 +
  63 + public void setAquaticType(Integer aquaticType) {
  64 + this.aquaticType = aquaticType;
  65 + }
  66 +
45 public void setId(Integer id) 67 public void setId(Integer id)
46 { 68 {
47 this.id = id; 69 this.id = id;
@@ -50,6 +50,11 @@ @@ -50,6 +50,11 @@
50 <groupId>org.jsoup</groupId> 50 <groupId>org.jsoup</groupId>
51 <artifactId>jsoup</artifactId> 51 <artifactId>jsoup</artifactId>
52 </dependency> 52 </dependency>
  53 + <!-- Tesseract OCR Java wrapper -->
  54 + <dependency>
  55 + <groupId>net.sourceforge.tess4j</groupId>
  56 + <artifactId>tess4j</artifactId>
  57 + </dependency>
53 </dependencies> 58 </dependencies>
54 59
55 </project> 60 </project>
1 package com.ruoyi.quartz.task; 1 package com.ruoyi.quartz.task;
2 2
  3 +import cn.hutool.core.text.UnicodeUtil;
  4 +import cn.hutool.http.HttpRequest;
3 import cn.hutool.http.HttpUtil; 5 import cn.hutool.http.HttpUtil;
  6 +import com.alibaba.fastjson.JSONArray;
4 import com.alibaba.fastjson.JSONObject; 7 import com.alibaba.fastjson.JSONObject;
5 import com.google.common.io.Resources; 8 import com.google.common.io.Resources;
6 import com.ruoyi.common.constant.Constants; 9 import com.ruoyi.common.constant.Constants;
7 import com.ruoyi.common.tool.SysLogininforType; 10 import com.ruoyi.common.tool.SysLogininforType;
8 import com.ruoyi.common.utils.DateUtils; 11 import com.ruoyi.common.utils.DateUtils;
9 import com.ruoyi.common.utils.MessageUtils; 12 import com.ruoyi.common.utils.MessageUtils;
  13 +import com.ruoyi.common.utils.StringUtils;
10 import com.ruoyi.quartz.task.aquatic.AquaticPublicOpinionBase; 14 import com.ruoyi.quartz.task.aquatic.AquaticPublicOpinionBase;
11 import com.ruoyi.quartz.task.aquatic.AquaticPublicOpinionService; 15 import com.ruoyi.quartz.task.aquatic.AquaticPublicOpinionService;
  16 +import com.ruoyi.quartz.task.aquatic.MpWeixinQqCom;
  17 +import com.ruoyi.system.domain.sys.SysConfig;
12 import com.zhonglai.luhui.dao.service.PublicService; 18 import com.zhonglai.luhui.dao.service.PublicService;
13 import com.zhonglai.luhui.sys.manager.AsyncManager; 19 import com.zhonglai.luhui.sys.manager.AsyncManager;
14 import com.zhonglai.luhui.sys.manager.factory.AsyncFactory; 20 import com.zhonglai.luhui.sys.manager.factory.AsyncFactory;
@@ -17,6 +23,9 @@ import org.slf4j.LoggerFactory; @@ -17,6 +23,9 @@ import org.slf4j.LoggerFactory;
17 import org.springframework.beans.factory.annotation.Autowired; 23 import org.springframework.beans.factory.annotation.Autowired;
18 import org.springframework.stereotype.Component; 24 import org.springframework.stereotype.Component;
19 25
  26 +import java.io.UnsupportedEncodingException;
  27 +import java.net.URLEncoder;
  28 +import java.nio.charset.StandardCharsets;
20 import java.nio.file.Files; 29 import java.nio.file.Files;
21 import java.nio.file.Path; 30 import java.nio.file.Path;
22 import java.nio.file.Paths; 31 import java.nio.file.Paths;
@@ -39,8 +48,17 @@ public class AquaticPublicOpinionTask { @@ -39,8 +48,17 @@ public class AquaticPublicOpinionTask {
39 private List<AquaticPublicOpinionService> aquaticPublicOpinionServices; 48 private List<AquaticPublicOpinionService> aquaticPublicOpinionServices;
40 @Autowired 49 @Autowired
41 protected PublicService publicService; 50 protected PublicService publicService;
  51 +
  52 + @Autowired
  53 + protected MpWeixinQqCom mpWeixinQqCom;
42 public void run(String day) 54 public void run(String day)
43 { 55 {
  56 + //初始微信采集服务
  57 + String cookie = publicService.getObject(SysConfig.class,"config_key","task.wx.cookie").getConfigValue();
  58 + String token = publicService.getObject(SysConfig.class,"config_key","task.wx.token").getConfigValue();
  59 + String user_agent = publicService.getObject(SysConfig.class,"config_key","task.wx.user_agent").getConfigValue();
  60 + mpWeixinQqCom.init(cookie,token,user_agent);
  61 +
44 for (AquaticPublicOpinionService service : aquaticPublicOpinionServices) { 62 for (AquaticPublicOpinionService service : aquaticPublicOpinionServices) {
45 AsyncManager.me().execute(new TimerTask() { 63 AsyncManager.me().execute(new TimerTask() {
46 @Override 64 @Override
@@ -59,11 +77,10 @@ public class AquaticPublicOpinionTask { @@ -59,11 +77,10 @@ public class AquaticPublicOpinionTask {
59 77
60 public void pushPublicOpinion() 78 public void pushPublicOpinion()
61 { 79 {
62 - List<Map<String,Object>> lsit = publicService.getObjectListBySQL("SELECT * FROM `fish_aquatic_public_opinion` WHERE release_time='"+DateUtils.getDate()+" 00:00:00'"); 80 + 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");
63 81
64 if (null != lsit && lsit.size()!=0) 82 if (null != lsit && lsit.size()!=0)
65 { 83 {
66 - List<AbstractMap.SimpleEntry<String, String>> dataList = new ArrayList<>();  
67 for (Map<String,Object> map:lsit) 84 for (Map<String,Object> map:lsit)
68 { 85 {
69 StringBuffer content = new StringBuffer(); 86 StringBuffer content = new StringBuffer();
  1 +package com.ruoyi.quartz.task.aquatic;
  2 +
  3 +import cn.hutool.http.HttpRequest;
  4 +import com.alibaba.fastjson.JSONArray;
  5 +import com.alibaba.fastjson.JSONObject;
  6 +import com.ruoyi.common.utils.DateUtils;
  7 +import com.ruoyi.common.utils.StringUtils;
  8 +import com.ruoyi.system.domain.fish.FishAquaticPublicOpinion;
  9 +import net.sourceforge.tess4j.Tesseract;
  10 +import org.jsoup.nodes.Document;
  11 +import org.jsoup.nodes.Element;
  12 +import org.jsoup.select.Elements;
  13 +import org.springframework.beans.factory.annotation.Value;
  14 +import org.springframework.stereotype.Service;
  15 +
  16 +import javax.imageio.ImageIO;
  17 +import java.awt.image.BufferedImage;
  18 +import java.io.IOException;
  19 +import java.io.InputStream;
  20 +import java.io.UnsupportedEncodingException;
  21 +import java.net.URL;
  22 +import java.net.URLEncoder;
  23 +import java.nio.charset.StandardCharsets;
  24 +import java.util.ArrayList;
  25 +import java.util.Date;
  26 +import java.util.List;
  27 +import java.util.Random;
  28 +
  29 +/**
  30 + * 微信公众号
  31 + * 由于依赖于 Tesseract OCR需要库支持
  32 + * ✅ 1. 安装 tesseract 本地依赖
  33 + * 如果你使用的是 Ubuntu / Debian:
  34 + *
  35 + * bash
  36 + * 复制
  37 + * 编辑
  38 + * sudo apt update
  39 + * sudo apt install tesseract-ocr libtesseract-dev
  40 + * 如果你使用的是 CentOS / RHEL:
  41 + *
  42 + * bash
  43 + * 复制
  44 + * 编辑
  45 + * sudo yum install tesseract tesseract-langpack-chi
  46 + * 2. 配置 Java 运行时加载 .so 的路径
  47 + * 启动 Java 应用时加上以下参数(假设你本地 .so 在 /usr/lib/x86_64-linux-gnu):
  48 + *
  49 + * bash
  50 + * 复制
  51 + * 编辑
  52 + * java -Djava.library.path=/usr/lib/x86_64-linux-gnu -jar yourapp.jar
  53 + * 或者设置环境变量:
  54 + *
  55 + * bash
  56 + * 复制
  57 + * 编辑
  58 + * export LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH
  59 + */
  60 +@Service
  61 +public class MpWeixinQqCom extends AquaticPublicOpinionBase{
  62 + 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";
  63 + private String token = "516447166";
  64 + 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";
  65 +
  66 + private static Tesseract tesseract = null;
  67 + @Value("${task.tesseract-path}")
  68 + private String tesseractPath;
  69 +
  70 +
  71 + private Tesseract getTesseract()
  72 + {
  73 + if(null == tesseract)
  74 + {
  75 + tesseract = new Tesseract();
  76 + // 放置 traineddata 的文件夹
  77 + tesseract.setDatapath(tesseractPath);
  78 + // 设置语言
  79 + tesseract.setLanguage("chi_sim+eng");
  80 + }
  81 + return tesseract;
  82 + }
  83 + public void init(String cookie,String token,String user_agent)
  84 + {
  85 + this.cookie = cookie;
  86 + this.token = token;
  87 + this.user_agent = user_agent;
  88 +
  89 + }
  90 + @Override
  91 + List<FishAquaticPublicOpinion> collect(String day) {
  92 + Random rand = new Random();
  93 +
  94 + List<FishAquaticPublicOpinion> list = new ArrayList<>();
  95 + String[] gzh = new String[]{"中国水产","水产前沿","淡水渔业","海大集团","海洋与渔业杂志","华中渔业研究社","科学养鱼","南京渔业科技","农民日报","水产养殖编辑部","UCN国际海产资讯","养殖前沿","壹渔业","中国渔业报"};
  96 + for (String name: gzh)
  97 + {
  98 + List<FishAquaticPublicOpinion> publist = getPublishList(day,name,2);
  99 + if (null != publist && publist.size()!=0)
  100 + {
  101 + list.addAll(publist);
  102 + }
  103 + try {
  104 + // 生成一个随机整数,范围是0到99
  105 + int randomInt = rand.nextInt(15);
  106 + Thread.sleep(randomInt*1000);
  107 + } catch (InterruptedException e) {
  108 + throw new RuntimeException(e);
  109 + }
  110 + }
  111 + String[] gzh_yj = new String[]{"海大农牧 水产一线","水产养殖网","农财宝典-大国渔业","大唐洋帆","禾晨科技","鱼市场","喜农和集团"};
  112 + for (String name: gzh_yj)
  113 + {
  114 + List<FishAquaticPublicOpinion> publist = getPublishList(day,name,3);
  115 + if (null != publist && publist.size()!=0)
  116 + {
  117 + list.addAll(publist);
  118 + }
  119 + try {
  120 + // 生成一个随机整数,范围是0到99
  121 + int randomInt = rand.nextInt(15);
  122 + Thread.sleep(randomInt*1000);
  123 + } catch (InterruptedException e) {
  124 + throw new RuntimeException(e);
  125 + }
  126 + }
  127 + return list;
  128 + }
  129 +
  130 + @Override
  131 + public String getInfo(String info_url) {
  132 + try {
  133 + Document doc = createDocument(info_url);
  134 + StringBuffer text = readtext(doc.getAllElements(),new StringBuffer(),"");
  135 + return text.toString();
  136 + } catch (IOException e) {
  137 + logger.error("解析详情错误:"+info_url,e);
  138 + }
  139 + return null;
  140 + }
  141 +
  142 + private StringBuffer readtext(Elements p,StringBuffer text,String backLine)
  143 + {
  144 + for (Element element : p) {
  145 + if (element.childrenSize()==0)
  146 + {
  147 + if (element.tagName().equals("img"))
  148 + {
  149 + String imgUrl = element.absUrl("data-src");
  150 + if (imgUrl == null || imgUrl.isEmpty()) {
  151 + imgUrl = element.absUrl("src");
  152 + if (!imgUrl.isEmpty()) {
  153 + String ocrText = doOCR(imgUrl);
  154 + if (StringUtils.isNotEmpty(ocrText)) {
  155 + text.append("[图片内容>] ").append(ocrText).append("[<图片内容]");
  156 + }
  157 + }
  158 + }
  159 + }else {
  160 + String line = element.text().trim();
  161 + if(StringUtils.isNotEmpty(line) && !line.equals(backLine))
  162 + {
  163 + text.append(line);
  164 +// text.append("\n");
  165 + backLine = line;
  166 + }
  167 + }
  168 + }else{
  169 + text = readtext(element.children(),text,backLine);
  170 + }
  171 + }
  172 + return text;
  173 + }
  174 +
  175 + private String doOCR(String imageUrl) {
  176 + logger.info("开始ocr解析:"+imageUrl);
  177 + try (InputStream in = new URL(imageUrl).openStream()) {
  178 + BufferedImage img = ImageIO.read(in);
  179 + if(null == img || img.getWidth()<500|| img.getHeight()<40)
  180 + {
  181 + return "";
  182 + }
  183 + logger.info("图片读取成功开始解析:"+img.getWidth()+"X"+img.getHeight());
  184 + String text = getTesseract().doOCR(img);
  185 + logger.info("结束ocr解析:"+imageUrl);
  186 + return text.trim().replaceAll("\\s+", " ");
  187 + } catch (Exception e) {
  188 + logger.error("OCR错误:"+imageUrl,e);
  189 + return "";
  190 + }
  191 + }
  192 +
  193 + private String getFakeid(String name)
  194 + {
  195 + String str = null;
  196 + try {
  197 + 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")
  198 + .header("User-Agent", user_agent)
  199 + .header("Cookie",cookie)
  200 + .execute().body();
  201 + } catch (UnsupportedEncodingException e) {
  202 + throw new RuntimeException(e) ;
  203 + }
  204 + JSONObject jsonObject = JSONObject.parseObject(str);
  205 + JSONArray jsonArray = jsonObject.getJSONArray("list");
  206 + if(null != jsonArray && jsonArray.size()!=0)
  207 + {
  208 + return jsonArray.getJSONObject(0).getString("fakeid");
  209 + }
  210 + logger.error("获取公众号fakeid失败:>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"+name);
  211 + return null;
  212 + }
  213 +
  214 + private List<FishAquaticPublicOpinion> getPublishList(String day,String name,Integer aquaticType)
  215 + {
  216 + String fakeid = getFakeid(name);
  217 + if (StringUtils.isNotEmpty(fakeid))
  218 + {
  219 + 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")
  220 + .header("user-agent", user_agent)
  221 + .header("cookie",cookie)
  222 + .execute().body();
  223 + System.out.println(str);
  224 + return parsePublish(day,str,aquaticType);
  225 + }
  226 + return null;
  227 + }
  228 +
  229 + /**
  230 + * 解析publish返回的消息
  231 + * @param str
  232 + */
  233 + public List<FishAquaticPublicOpinion> parsePublish(String day,String str,Integer aquaticType)
  234 + {
  235 + JSONObject jsonObject = JSONObject.parseObject(str);
  236 + System.out.println(str);
  237 + JSONObject publish_page = JSONObject.parseObject(jsonObject.getString("publish_page"));
  238 + System.out.println(publish_page);
  239 + JSONArray jsonArray = publish_page.getJSONArray("publish_list");
  240 + if (null != jsonArray && jsonArray.size()!=0)
  241 + {
  242 + List<FishAquaticPublicOpinion> list = new ArrayList<>();
  243 + try {
  244 + for (int i = 0; i < jsonArray.size(); i++) {
  245 + JSONObject publish = jsonArray.getJSONObject(i);
  246 + JSONObject publish_info = JSONObject.parseObject(publish.getString("publish_info"));
  247 + System.out.println(publish_info);
  248 + JSONArray appmsgex = publish_info.getJSONArray("appmsgex");
  249 + System.out.println(appmsgex);
  250 + if (null != appmsgex && appmsgex.size() != 0) {
  251 + JSONObject appmsg = appmsgex.getJSONObject(0);
  252 + String title = appmsg.getString("title");
  253 + Integer update_time = appmsg.getInteger("update_time");
  254 + Date timeday = new Date(update_time * 1000l);
  255 + String time = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, timeday);
  256 + if (StringUtils.isNotEmpty(day) && DateUtils.parseDate(time, DateUtils.YYYY_MM_DD).equals(DateUtils.parseDate(day, DateUtils.YYYY_MM_DD))) {
  257 + String info_url = appmsg.getString("link");
  258 + FishAquaticPublicOpinion aquaticPublicOpinion = new FishAquaticPublicOpinion();
  259 + aquaticPublicOpinion.setTitle(title);
  260 + aquaticPublicOpinion.setInfoUrl(info_url);
  261 + aquaticPublicOpinion.setReleaseTime(timeday);
  262 + aquaticPublicOpinion.setCreateTime(new Date());
  263 + aquaticPublicOpinion.setAquaticType(aquaticType);
  264 + list.add(aquaticPublicOpinion);
  265 + } else {
  266 + return list;
  267 + }
  268 +
  269 + }
  270 + }
  271 + } catch (Exception e) {
  272 + logger.error("数据解析错误:"+str,e);
  273 + }
  274 + return list;
  275 + }
  276 + return null;
  277 + }
  278 + public static void main(String[] args) {
  279 + MpWeixinQqCom mpWeixinQqCom = new MpWeixinQqCom();
  280 + System.out.println(mpWeixinQqCom.getInfo("https://mp.weixin.qq.com/s/4yygkdF9dnPZc2TUgILKZA"));
  281 +// mpWeixinQqCom.getPublishList("2025-07-22","水产养殖网",3);
  282 + }
  283 +}
@@ -43,6 +43,7 @@ public class WwwCafsAcCn extends AquaticPublicOpinionBase{ @@ -43,6 +43,7 @@ public class WwwCafsAcCn extends AquaticPublicOpinionBase{
43 aquaticPublicOpinion.setInfoUrl(url); 43 aquaticPublicOpinion.setInfoUrl(url);
44 aquaticPublicOpinion.setReleaseTime(DateUtils.parseDate(time,"yyyy年MM月dd日")); 44 aquaticPublicOpinion.setReleaseTime(DateUtils.parseDate(time,"yyyy年MM月dd日"));
45 aquaticPublicOpinion.setCreateTime(new Date()); 45 aquaticPublicOpinion.setCreateTime(new Date());
  46 + aquaticPublicOpinion.setAquaticType(1);
46 list.add(aquaticPublicOpinion); 47 list.add(aquaticPublicOpinion);
47 }else{ 48 }else{
48 return list; 49 return list;
@@ -51,6 +51,7 @@ public class WwwChinaCfaOrg extends AquaticPublicOpinionBase{ @@ -51,6 +51,7 @@ public class WwwChinaCfaOrg extends AquaticPublicOpinionBase{
51 aquaticPublicOpinion.setInfoUrl(url); 51 aquaticPublicOpinion.setInfoUrl(url);
52 aquaticPublicOpinion.setReleaseTime(DateUtils.parseDate(time,DateUtils.YYYY_MM_DD)); 52 aquaticPublicOpinion.setReleaseTime(DateUtils.parseDate(time,DateUtils.YYYY_MM_DD));
53 aquaticPublicOpinion.setCreateTime(new Date()); 53 aquaticPublicOpinion.setCreateTime(new Date());
  54 + aquaticPublicOpinion.setAquaticType(1);
54 list.add(aquaticPublicOpinion); 55 list.add(aquaticPublicOpinion);
55 }else{ 56 }else{
56 return list; 57 return list;
@@ -41,6 +41,7 @@ public class WwwCsfishOrgCn extends AquaticPublicOpinionBase{ @@ -41,6 +41,7 @@ public class WwwCsfishOrgCn extends AquaticPublicOpinionBase{
41 aquaticPublicOpinion.setInfoUrl(url); 41 aquaticPublicOpinion.setInfoUrl(url);
42 aquaticPublicOpinion.setReleaseTime(DateUtils.parseDate(time,DateUtils.YYYY_MM_DD)); 42 aquaticPublicOpinion.setReleaseTime(DateUtils.parseDate(time,DateUtils.YYYY_MM_DD));
43 aquaticPublicOpinion.setCreateTime(new Date()); 43 aquaticPublicOpinion.setCreateTime(new Date());
  44 + aquaticPublicOpinion.setAquaticType(1);
44 list.add(aquaticPublicOpinion); 45 list.add(aquaticPublicOpinion);
45 }else { 46 }else {
46 return list; 47 return list;
@@ -92,6 +92,7 @@ public class WwwMoaGovCn extends AquaticPublicOpinionBase{ @@ -92,6 +92,7 @@ public class WwwMoaGovCn extends AquaticPublicOpinionBase{
92 aquaticPublicOpinion.setInfoUrl(url); 92 aquaticPublicOpinion.setInfoUrl(url);
93 aquaticPublicOpinion.setReleaseTime(DateUtils.parseDate(time,DateUtils.YYYY_MM_DD)); 93 aquaticPublicOpinion.setReleaseTime(DateUtils.parseDate(time,DateUtils.YYYY_MM_DD));
94 aquaticPublicOpinion.setCreateTime(new Date()); 94 aquaticPublicOpinion.setCreateTime(new Date());
  95 + aquaticPublicOpinion.setAquaticType(1);
95 list.add(aquaticPublicOpinion); 96 list.add(aquaticPublicOpinion);
96 }else { 97 }else {
97 return list; 98 return list;
@@ -56,6 +56,7 @@ public class WwwNftecAgriCn extends AquaticPublicOpinionBase{ @@ -56,6 +56,7 @@ public class WwwNftecAgriCn extends AquaticPublicOpinionBase{
56 aquaticPublicOpinion.setInfoUrl(url); 56 aquaticPublicOpinion.setInfoUrl(url);
57 aquaticPublicOpinion.setReleaseTime(DateUtils.parseDate(time,DateUtils.YYYY_MM_DD)); 57 aquaticPublicOpinion.setReleaseTime(DateUtils.parseDate(time,DateUtils.YYYY_MM_DD));
58 aquaticPublicOpinion.setCreateTime(new Date()); 58 aquaticPublicOpinion.setCreateTime(new Date());
  59 + aquaticPublicOpinion.setAquaticType(1);
59 list.add(aquaticPublicOpinion); 60 list.add(aquaticPublicOpinion);
60 }else { 61 }else {
61 return list; 62 return list;
  1 +package com.ruoyi.quartz.util;
  2 +
  3 +public class FishPrice {
  4 + public String 品种;
  5 + public String 地区;
  6 + public String 时间;
  7 + public String 规格;
  8 + public String 价格;
  9 +}
  1 +package com.ruoyi.quartz.util;
  2 +
  3 +import com.fasterxml.jackson.core.JsonProcessingException;
  4 +import com.fasterxml.jackson.core.type.TypeReference;
  5 +import com.fasterxml.jackson.databind.ObjectMapper;
  6 +import com.ruoyi.common.utils.DateUtils;
  7 +
  8 +import javax.imageio.ImageIO;
  9 +import java.awt.*;
  10 +import java.awt.image.BufferedImage;
  11 +import java.io.File;
  12 +import java.io.IOException;
  13 +import java.text.SimpleDateFormat;
  14 +import java.util.*;
  15 +import java.util.List;
  16 +
  17 +public class FishPriceTableWithLogo {
  18 +
  19 + public static void main(String[] args) throws Exception {
  20 + 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斤\"}]";
  21 + 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斤\"}]";
  22 + // 每个地区维护一个 Set 用于去重
  23 + Map<String, Set<String>> dedupMap = new HashMap<>();
  24 + // 按地区分组
  25 + Map<String, List<FishPrice>> grouped = new LinkedHashMap<>();
  26 + addFishPrice(grouped,json,dedupMap);
  27 + addFishPrice(grouped,json2,dedupMap);
  28 + draw("2025年7月23日",grouped);
  29 + }
  30 +
  31 + public static final Font baseFont;
  32 +
  33 + static {
  34 + try {
  35 + baseFont = Font.createFont(Font.TRUETYPE_FONT,
  36 + new File("notosans/NotoSansCJKsc-Medium.otf"));
  37 + } catch (FontFormatException e) {
  38 + throw new RuntimeException(e);
  39 + } catch (IOException e) {
  40 + throw new RuntimeException(e);
  41 + }
  42 + }
  43 +
  44 +
  45 + public static void addFishPrice(Map<String, List<FishPrice>> grouped, String json,Map<String, Set<String>> dedupMap ) throws JsonProcessingException {
  46 + ObjectMapper mapper = new ObjectMapper();
  47 + List<FishPrice> list = mapper.readValue(json, new TypeReference<List<FishPrice>>() {});
  48 +
  49 +
  50 + for (FishPrice item : list) {
  51 + String region = item.地区;
  52 + String uniqueKey = (item.品种 + "|" + item.规格 + "|" + item.价格 + "|" + item.时间).trim();
  53 +
  54 + // 初始化分组和去重集合
  55 + grouped.computeIfAbsent(region, k -> new ArrayList<>());
  56 + dedupMap.computeIfAbsent(region, k -> new HashSet<>());
  57 +
  58 + // 去重:只添加未出现过的记录
  59 + if (!dedupMap.get(region).contains(uniqueKey)) {
  60 + grouped.get(region).add(item);
  61 + dedupMap.get(region).add(uniqueKey);
  62 + }
  63 + }
  64 + }
  65 +
  66 + public static String draw(String day, Map<String, List<FishPrice>> grouped) throws Exception {
  67 + // 表格参数
  68 + int rowHeight = 30;
  69 + int colWidth = 180;
  70 + int paddingTop = 140;
  71 + int paddingBottom = 120;
  72 + int tableWidth = colWidth * 5;
  73 + int tableHeight = (grouped.values().stream().mapToInt(List::size).sum() + 1) * rowHeight;
  74 + int totalHeight = paddingTop + tableHeight + paddingBottom;
  75 +
  76 + // 创建画布
  77 + BufferedImage image = new BufferedImage(tableWidth, totalHeight, BufferedImage.TYPE_INT_RGB);
  78 + Graphics2D g = image.createGraphics();
  79 +
  80 + // 背景色
  81 + g.setColor(new Color(235, 245, 255)); // 浅蓝
  82 + g.fillRect(0, 0, tableWidth, totalHeight);
  83 +
  84 + // 抗锯齿
  85 + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
  86 +
  87 + // 标题
  88 + g.setColor(Color.BLACK);
  89 + g.setFont(baseFont.deriveFont( Font.BOLD, 28));
  90 + drawCenteredString(g, day+" 全国主要鱼价行情表", new Rectangle(0, 20, tableWidth, 40), g.getFont());
  91 +
  92 + // 绘制表格头
  93 + g.setFont(baseFont.deriveFont(Font.PLAIN, 16));
  94 + g.setColor(Color.BLACK);
  95 + String[] headers = {"地区", "品种", "规格", "价格", "时间"};
  96 + for (int i = 0; i < headers.length; i++) {
  97 + g.drawRect(i * colWidth, paddingTop, colWidth, rowHeight);
  98 + g.drawString(headers[i], i * colWidth + 10, paddingTop + 20);
  99 + }
  100 +
  101 + // 填充表格数据
  102 + int y = paddingTop + rowHeight;
  103 + for (Map.Entry<String, List<FishPrice>> entry : grouped.entrySet()) {
  104 + String region = entry.getKey();
  105 + List<FishPrice> fishes = entry.getValue();
  106 +
  107 + // 地区合并绘制
  108 + g.drawRect(0, y, colWidth, fishes.size() * rowHeight);
  109 + drawCenteredString(g, region, new Rectangle(0, y, colWidth, fishes.size() * rowHeight), g.getFont());
  110 +
  111 + for (int i = 0; i < fishes.size(); i++) {
  112 + FishPrice f = fishes.get(i);
  113 + int rowY = y + i * rowHeight;
  114 +
  115 + g.drawRect(colWidth * 1, rowY, colWidth, rowHeight);
  116 + g.drawRect(colWidth * 2, rowY, colWidth, rowHeight);
  117 + g.drawRect(colWidth * 3, rowY, colWidth, rowHeight);
  118 + g.drawRect(colWidth * 4, rowY, colWidth, rowHeight);
  119 +
  120 + g.drawString(null==f.品种?" ":f.品种, colWidth * 1 + 10, rowY + 20);
  121 + g.drawString(null==f.规格?" ":f.规格, colWidth * 2 + 10, rowY + 20);
  122 + g.drawString(null==f.价格?" ":f.价格, colWidth * 3 + 10, rowY + 20);
  123 + g.drawString(safeDate(f.时间), colWidth * 4 + 10, rowY + 20);
  124 + }
  125 + y += fishes.size() * rowHeight;
  126 + }
  127 +
  128 + // 绘制下方 Logo 图片
  129 + BufferedImage logoImg = ImageIO.read(new File("logo.png"));
  130 + int logoWidth = logoImg.getWidth();
  131 + int logoHeight = logoImg.getHeight();
  132 + int logoX = (tableWidth - logoWidth) / 2;
  133 + int logoY = totalHeight - logoHeight - 40;
  134 + g.drawImage(logoImg, logoX, logoY, null);
  135 +
  136 + // 上方文字 Logo
  137 + g.setFont(baseFont.deriveFont(Font.ITALIC, 16));
  138 + drawCenteredString(g, "由鱼儿乐智慧渔业出品", new Rectangle(logoX, logoY+logoHeight, 60, 20), g.getFont());
  139 +
  140 + g.dispose();
  141 +
  142 + // 输出图片
  143 + String saveFile = StyleConfig.savePath + new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()) + "_jiage.png";
  144 +
  145 + ImageIO.write(image, "png", new File(saveFile));
  146 + System.out.println("✅ 成功生成图片: "+saveFile);
  147 + return saveFile;
  148 + }
  149 +
  150 + static void drawCenteredString(Graphics2D g, String text, Rectangle rect, Font font) {
  151 + if (null == text)
  152 + {
  153 + text = "";
  154 + }
  155 + FontMetrics metrics = g.getFontMetrics(font);
  156 + try {
  157 + int x = rect.x + (rect.width - metrics.stringWidth(text)) / 2;
  158 + int y = rect.y + ((rect.height - metrics.getHeight()) / 2) + metrics.getAscent();
  159 + g.setFont(font);
  160 + g.drawString(text, x, y);
  161 + }catch (Exception e)
  162 + {
  163 + System.out.println(text);
  164 + }
  165 +
  166 + }
  167 +
  168 + static String safeDate(String date) {
  169 + if (date == null || !date.matches("\\d{4}-\\d{2}-\\d{2}")) {
  170 + return "未知";
  171 + }
  172 + return date;
  173 + }
  174 +}
  175 +
1 package com.ruoyi.quartz.util; 1 package com.ruoyi.quartz.util;
2 2
3 -import com.google.common.io.Resources;  
4 -import com.ruoyi.common.utils.DateUtils;  
5 -  
6 import javax.imageio.ImageIO; 3 import javax.imageio.ImageIO;
7 import java.awt.*; 4 import java.awt.*;
8 import java.awt.image.BufferedImage; 5 import java.awt.image.BufferedImage;
9 -import java.awt.AlphaComposite;  
10 import java.io.File; 6 import java.io.File;
11 import java.io.IOException; 7 import java.io.IOException;
  8 +import java.text.SimpleDateFormat;
12 import java.util.*; 9 import java.util.*;
13 import java.util.List; 10 import java.util.List;
14 11
15 public class MultiTextImageGenerator { 12 public class MultiTextImageGenerator {
16 13
17 - private static final String savePath = "uploadPath/aquaticPublicOpinion/img/";  
18 - private static final String backgroundPath = "moban.png"; 14 +
19 public static void main(String[] args) throws IOException { 15 public static void main(String[] args) throws IOException {
20 // 1. 示例数据 16 // 1. 示例数据
21 List<AbstractMap.SimpleEntry<String, String>> dataList = Arrays.asList( 17 List<AbstractMap.SimpleEntry<String, String>> dataList = Arrays.asList(
@@ -26,63 +22,111 @@ public class MultiTextImageGenerator { @@ -26,63 +22,111 @@ public class MultiTextImageGenerator {
26 "期刊集群建设研讨会。中国水产学会主办" + 22 "期刊集群建设研讨会。中国水产学会主办" +
27 ",70余位代表参会,聚焦期刊建设与智" + 23 ",70余位代表参会,聚焦期刊建设与智" +
28 "慧渔业,搭建合作平台,注入新动能,奠" + 24 "慧渔业,搭建合作平台,注入新动能,奠" +
  25 + "定发展基础。"),
  26 + new AbstractMap.SimpleEntry<>("水产期刊集群研讨召开", "2025年7月3日,吉林省长春市举办水产" +
  27 + "期刊集群建设研讨会。中国水产学会主办" +
  28 + ",70余位代表参会,聚焦期刊建设与智" +
  29 + "慧渔业,搭建合作平台,注入新动能,奠" +
  30 + "定发展基础。"),
  31 + new AbstractMap.SimpleEntry<>("水产期刊集群研讨召开", "2025年7月3日,吉林省长春市举办水产" +
  32 + "期刊集群建设研讨会。中国水产学会主办" +
  33 + ",70余位代表参会,聚焦期刊建设与智" +
  34 + "慧渔业,搭建合作平台,注入新动能,奠" +
  35 + "定发展基础。"),
  36 + new AbstractMap.SimpleEntry<>("水产期刊集群研讨召开", "2025年7月3日,吉林省长春市举办水产" +
  37 + "期刊集群建设研讨会。中国水产学会主办" +
  38 + ",70余位代表参会,聚焦期刊建设与智" +
  39 + "慧渔业,搭建合作平台,注入新动能,奠" +
29 "定发展基础。") 40 "定发展基础。")
30 ); 41 );
31 // 2. 配置加载 42 // 2. 配置加载
32 generateImage(dataList,new StyleConfig()); 43 generateImage(dataList,new StyleConfig());
33 } 44 }
34 45
  46 + /**
  47 + * 根据传入的数据列表和样式配置生成一张带有背景图的图像。
  48 + * 背景图分为顶部 logo、中部纯色、底部 logo 三部分。
  49 + * 中间内容区根据数据动态拉伸,高度自动适配。
  50 + */
35 public static String generateImage(List<AbstractMap.SimpleEntry<String, String>> dataList, StyleConfig style) throws IOException { 51 public static String generateImage(List<AbstractMap.SimpleEntry<String, String>> dataList, StyleConfig style) throws IOException {
  52 + // 读取背景图
  53 + BufferedImage baseImage = ImageIO.read(new File(style.backgroundPath));
  54 + int width = baseImage.getWidth();
  55 + int maxTextWidth = width - 2 * style.startX;
36 56
37 -  
38 - // 3. 背景图  
39 - BufferedImage image = ImageIO.read(new File(backgroundPath));  
40 - int width = image.getWidth();  
41 - int height = image.getHeight();  
42 - BufferedImage newImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); 57 + // 取背景图中部颜色作为中间背景区的纯色背景
  58 + int centerY = baseImage.getHeight() / 2;
  59 + int centerColorRGB = baseImage.getRGB(width / 2, centerY);
  60 + Color pureBackgroundColor = new Color(centerColorRGB);
  61 +
  62 + // 创建临时画布用于计算文字高度
  63 + BufferedImage tempImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
  64 + Graphics2D tempG = tempImage.createGraphics();
  65 + tempG.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
  66 + tempG.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
  67 + int contentHeight = calculateTotalHeight(dataList, style, tempG, maxTextWidth); // 动态内容高度
  68 + tempG.dispose();
  69 +
  70 + // 新图总高度 = 顶部logo + 内容区 + 底部logo
  71 + int finalHeight = style.topLogoHeight + contentHeight + style.bottomLogoHeight;
  72 + BufferedImage newImage = new BufferedImage(width, finalHeight, BufferedImage.TYPE_INT_ARGB);
43 Graphics2D g = newImage.createGraphics(); 73 Graphics2D g = newImage.createGraphics();
44 - g.drawImage(image, 0, 0, null);  
45 74
46 - // 抗锯齿 75 + // 绘制顶部 logo 区域
  76 + BufferedImage topPart = baseImage.getSubimage(0, 0, width, style.topLogoHeight);
  77 + g.drawImage(topPart, 0, 0, null);
  78 +
  79 + // 绘制底部 logo 区域
  80 + BufferedImage bottomPart = baseImage.getSubimage(0, baseImage.getHeight() - style.bottomLogoHeight, width, style.bottomLogoHeight);
  81 + g.drawImage(bottomPart, 0, finalHeight - style.bottomLogoHeight, null);
  82 +
  83 + // 绘制中部纯色背景(中间拉伸区域)
  84 + g.setColor(pureBackgroundColor);
  85 + g.fillRect(0, style.topLogoHeight, width, contentHeight);
  86 +
  87 + // 开启抗锯齿和文字抗锯齿
47 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 88 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
48 g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 89 g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
49 90
50 - int maxTextWidth = width - 2 * style.startX;  
51 - int currY = style.startY; 91 + // 内容绘制起始 Y 坐标
  92 + int currY = style.topLogoHeight + style.startY;
52 93
53 - // 4. 绘制每组内容块  
54 for (int i = 0; i < dataList.size(); i++) { 94 for (int i = 0; i < dataList.size(); i++) {
55 - String title = (i + 1) + ". " + dataList.get(i).getKey();  
56 - String content = dataList.get(i).getValue(); 95 + String title = (i + 1) + ". " + dataList.get(i).getKey(); // 标题编号 + 标题
  96 + String content = dataList.get(i).getValue(); // 内容文本
57 97
  98 + // 获取标题高度
58 g.setFont(style.titleFont); 99 g.setFont(style.titleFont);
59 FontMetrics titleMetrics = g.getFontMetrics(); 100 FontMetrics titleMetrics = g.getFontMetrics();
60 int titleHeight = titleMetrics.getHeight(); 101 int titleHeight = titleMetrics.getHeight();
61 102
  103 + // 获取内容文本行
62 g.setFont(style.contentFont); 104 g.setFont(style.contentFont);
63 FontMetrics contentMetrics = g.getFontMetrics(); 105 FontMetrics contentMetrics = g.getFontMetrics();
64 List<String> lines = splitTextIntoLines(content, contentMetrics, maxTextWidth); 106 List<String> lines = splitTextIntoLines(content, contentMetrics, maxTextWidth);
65 int lineHeight = contentMetrics.getHeight() + style.lineSpacing; 107 int lineHeight = contentMetrics.getHeight() + style.lineSpacing;
66 - int contentHeight = lines.size() * lineHeight;  
67 - int totalTextHeight = titleHeight + 10 + contentHeight; 108 + int contentBlockHeight = lines.size() * lineHeight;
  109 +
  110 + // 整体文字高度 = 标题 + 间距 + 内容块
  111 + int totalTextHeight = titleHeight + 10 + contentBlockHeight;
68 int blockHeight = totalTextHeight + 2 * style.padding; 112 int blockHeight = totalTextHeight + 2 * style.padding;
69 113
70 int blockWidth = maxTextWidth + 2 * style.padding; 114 int blockWidth = maxTextWidth + 2 * style.padding;
71 int blockX = style.startX - style.padding; 115 int blockX = style.startX - style.padding;
72 int blockY = currY; 116 int blockY = currY;
73 117
74 - if (style.showBackgroundBlock)  
75 - { 118 + // 绘制圆角背景块(可配置是否显示)
  119 + if (style.showBackgroundBlock) {
76 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, style.backgroundAlpha)); 120 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, style.backgroundAlpha));
77 g.setColor(style.backgroundColor); 121 g.setColor(style.backgroundColor);
78 g.fillRoundRect(blockX, blockY, blockWidth, blockHeight, style.cornerRadius, style.cornerRadius); 122 g.fillRoundRect(blockX, blockY, blockWidth, blockHeight, style.cornerRadius, style.cornerRadius);
79 } 123 }
80 124
  125 + // 恢复正常透明度,绘制文本内容
81 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f)); 126 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f));
82 g.setColor(style.textColor); 127 g.setColor(style.textColor);
83 128
84 - int textStartY = blockY + style.padding + (blockHeight - 2 * style.padding - totalTextHeight) / 2;  
85 - 129 + int textStartY = blockY + style.padding;
86 g.setFont(style.titleFont); 130 g.setFont(style.titleFont);
87 int titleY = textStartY + titleHeight; 131 int titleY = textStartY + titleHeight;
88 g.drawString(title, style.startX, titleY); 132 g.drawString(title, style.startX, titleY);
@@ -94,24 +138,22 @@ public class MultiTextImageGenerator { @@ -94,24 +138,22 @@ public class MultiTextImageGenerator {
94 g.drawString(lines.get(j), style.startX, y); 138 g.drawString(lines.get(j), style.startX, y);
95 } 139 }
96 140
97 - currY = blockY + blockHeight + style.blockSpacing; 141 + currY = blockY + blockHeight + style.blockSpacing; // 下一个块起始位置
98 } 142 }
99 143
100 - // 5. 添加文字水印  
101 - drawTextWatermark(g, width, height, style);  
102 -  
103 - // 6. 添加图片水印  
104 -// drawImageWatermark(g, width, height, style);  
105 - 144 + // 绘制水印文字(右下角)
  145 + drawTextWatermark(g, width, finalHeight, style);
106 g.dispose(); 146 g.dispose();
107 147
108 - // 7. 保存输出  
109 - String saveFile = savePath + DateUtils.getDate()+ "_output.png"; 148 + // 生成保存文件名
  149 + String saveFile = style.savePath + new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()) + "_xinwen.png";
110 ImageIO.write(newImage, "png", new File(saveFile)); 150 ImageIO.write(newImage, "png", new File(saveFile));
111 return saveFile; 151 return saveFile;
112 } 152 }
113 153
114 - // 文字自动换行 154 + /**
  155 + * 将文本按像素宽度进行自动换行
  156 + */
115 private static List<String> splitTextIntoLines(String text, FontMetrics metrics, int maxWidth) { 157 private static List<String> splitTextIntoLines(String text, FontMetrics metrics, int maxWidth) {
116 List<String> lines = new ArrayList<>(); 158 List<String> lines = new ArrayList<>();
117 StringBuilder line = new StringBuilder(); 159 StringBuilder line = new StringBuilder();
@@ -129,7 +171,34 @@ public class MultiTextImageGenerator { @@ -129,7 +171,34 @@ public class MultiTextImageGenerator {
129 return lines; 171 return lines;
130 } 172 }
131 173
132 - // 文字水印 174 + /**
  175 + * 计算总内容区所需高度(含块间距)
  176 + */
  177 + private static int calculateTotalHeight(List<AbstractMap.SimpleEntry<String, String>> dataList, StyleConfig style, Graphics2D g, int maxTextWidth) {
  178 + int currY = style.startY;
  179 +
  180 + g.setFont(style.titleFont);
  181 + FontMetrics titleMetrics = g.getFontMetrics();
  182 + int titleHeight = titleMetrics.getHeight();
  183 +
  184 + g.setFont(style.contentFont);
  185 + FontMetrics contentMetrics = g.getFontMetrics();
  186 + int lineHeight = contentMetrics.getHeight() + style.lineSpacing;
  187 +
  188 + for (AbstractMap.SimpleEntry<String, String> entry : dataList) {
  189 + List<String> lines = splitTextIntoLines(entry.getValue(), contentMetrics, maxTextWidth);
  190 + int contentHeight = lines.size() * lineHeight;
  191 + int totalTextHeight = titleHeight + 10 + contentHeight;
  192 + int blockHeight = totalTextHeight + 2 * style.padding;
  193 + currY += blockHeight + style.blockSpacing;
  194 + }
  195 +
  196 + return currY;
  197 + }
  198 +
  199 + /**
  200 + * 绘制右下角文字水印
  201 + */
133 private static void drawTextWatermark(Graphics2D g, int width, int height, StyleConfig style) { 202 private static void drawTextWatermark(Graphics2D g, int width, int height, StyleConfig style) {
134 g.setFont(style.watermarkFont); 203 g.setFont(style.watermarkFont);
135 g.setColor(style.watermarkColor); 204 g.setColor(style.watermarkColor);
@@ -140,26 +209,5 @@ public class MultiTextImageGenerator { @@ -140,26 +209,5 @@ public class MultiTextImageGenerator {
140 g.drawString(style.watermarkText, x, y); 209 g.drawString(style.watermarkText, x, y);
141 } 210 }
142 211
143 - // 图片水印  
144 - private static void drawImageWatermark(Graphics2D g, int width, int height, StyleConfig style) {  
145 - try {  
146 - String watermarkImagePath = style.watermarkImagePath;  
147 - File file = new File(watermarkImagePath);  
148 - if (!file.exists()) {  
149 - System.out.println("⚠️ 水印图片未找到:" + watermarkImagePath);  
150 - return;  
151 - }  
152 - Image logo = ImageIO.read(file).getScaledInstance(  
153 - style.watermarkImageWidth,  
154 - style.watermarkImageHeight,  
155 - Image.SCALE_SMOOTH  
156 - );  
157 - int x = width - style.watermarkImageWidth - style.watermarkImageMarginX;  
158 - int y = height - style.watermarkImageHeight - style.watermarkImageMarginY;  
159 - g.drawImage(logo, x, y, null);  
160 - } catch (IOException e) {  
161 - System.err.println("❌ 加载图片水印失败:" + e.getMessage());  
162 - }  
163 - }  
164 } 212 }
165 213
@@ -7,6 +7,7 @@ import java.io.File; @@ -7,6 +7,7 @@ import java.io.File;
7 * 样式配置类 7 * 样式配置类
8 */ 8 */
9 public class StyleConfig { 9 public class StyleConfig {
  10 + public static final String savePath = "uploadPath/aquaticPublicOpinion/img/";
10 public StyleConfig() { 11 public StyleConfig() {
11 try { 12 try {
12 Font baseFont = Font.createFont(Font.TRUETYPE_FONT, 13 Font baseFont = Font.createFont(Font.TRUETYPE_FONT,
@@ -23,6 +24,9 @@ public class StyleConfig { @@ -23,6 +24,9 @@ public class StyleConfig {
23 contentFont = new Font("Serif", Font.PLAIN, 28); 24 contentFont = new Font("Serif", Font.PLAIN, 28);
24 } 25 }
25 } 26 }
  27 + public String backgroundPath = "moban.png";
  28 + public int topLogoHeight = 245; //顶部保留高度
  29 + public int bottomLogoHeight = 150; // 底部保留高度
26 30
27 // 文本样式配置 31 // 文本样式配置
28 public Font titleFont = new Font("微软雅黑", Font.BOLD, 36); 32 public Font titleFont = new Font("微软雅黑", Font.BOLD, 36);
@@ -56,7 +60,7 @@ public class StyleConfig { @@ -56,7 +60,7 @@ public class StyleConfig {
56 public int startX = 97; 60 public int startX = 97;
57 // 文字区域的起始横坐标(左边距) 61 // 文字区域的起始横坐标(左边距)
58 62
59 - public int startY = 245; 63 + public int startY = 0;
60 // 第一段文字区域的起始纵坐标(上边距) 64 // 第一段文字区域的起始纵坐标(上边距)
61 65
62 public int lineSpacing = 5; 66 public int lineSpacing = 5;
@@ -94,5 +98,7 @@ public class StyleConfig { @@ -94,5 +98,7 @@ public class StyleConfig {
94 public int watermarkImageMarginY = 20; 98 public int watermarkImageMarginY = 20;
95 // 图片水印的纵向偏移量(从右下角向内缩进的距离) 99 // 图片水印的纵向偏移量(从右下角向内缩进的距离)
96 100
  101 +
  102 +
97 } 103 }
98 104
@@ -10,8 +10,12 @@ import com.ruoyi.common.utils.GsonConstructor; @@ -10,8 +10,12 @@ import com.ruoyi.common.utils.GsonConstructor;
10 import com.ruoyi.common.utils.ServletUtils; 10 import com.ruoyi.common.utils.ServletUtils;
11 import com.ruoyi.common.utils.ip.IpUtils; 11 import com.ruoyi.common.utils.ip.IpUtils;
12 import com.ruoyi.framework.web.domain.Server; 12 import com.ruoyi.framework.web.domain.Server;
  13 +import com.ruoyi.quartz.task.aquatic.MpWeixinQqCom;
  14 +import com.ruoyi.quartz.util.FishPrice;
  15 +import com.ruoyi.quartz.util.FishPriceTableWithLogo;
13 import com.ruoyi.quartz.util.MultiTextImageGenerator; 16 import com.ruoyi.quartz.util.MultiTextImageGenerator;
14 import com.ruoyi.quartz.util.StyleConfig; 17 import com.ruoyi.quartz.util.StyleConfig;
  18 +import com.ruoyi.system.domain.fish.FishAquaticPublicOpinion;
15 import com.ruoyi.system.domain.sys.SysMonitorApplication; 19 import com.ruoyi.system.domain.sys.SysMonitorApplication;
16 import com.ruoyi.system.domain.sys.SysMonitorApplicationLog; 20 import com.ruoyi.system.domain.sys.SysMonitorApplicationLog;
17 import com.ruoyi.system.domain.sys.SysMonitorServer; 21 import com.ruoyi.system.domain.sys.SysMonitorServer;
@@ -25,7 +29,7 @@ import com.zhonglai.luhui.admin.qywx.WXBizMsgCrypt; @@ -25,7 +29,7 @@ import com.zhonglai.luhui.admin.qywx.WXBizMsgCrypt;
25 import com.zhonglai.luhui.admin.service.SendSysMonitorServerMessge; 29 import com.zhonglai.luhui.admin.service.SendSysMonitorServerMessge;
26 import com.zhonglai.luhui.dao.service.PublicService; 30 import com.zhonglai.luhui.dao.service.PublicService;
27 import com.zhonglai.luhui.device.analysis.comm.util.StringUtils; 31 import com.zhonglai.luhui.device.analysis.comm.util.StringUtils;
28 -import com.zhonglai.luhui.admin.dto.GenerateAquaticPublicOpinionPosterRq; 32 +import com.zhonglai.luhui.sys.manager.AsyncManager;
29 import io.swagger.annotations.Api; 33 import io.swagger.annotations.Api;
30 import io.swagger.annotations.ApiOperation; 34 import io.swagger.annotations.ApiOperation;
31 import org.springframework.beans.factory.annotation.Autowired; 35 import org.springframework.beans.factory.annotation.Autowired;
@@ -69,6 +73,9 @@ public class ServerController extends BaseController @@ -69,6 +73,9 @@ public class ServerController extends BaseController
69 @Autowired 73 @Autowired
70 private ScheduledExecutorService scheduledExecutorService; 74 private ScheduledExecutorService scheduledExecutorService;
71 75
  76 + @Autowired
  77 + private MpWeixinQqCom mpWeixinQqCom;
  78 +
72 @ApiOperation("获取详情") 79 @ApiOperation("获取详情")
73 @PreAuthorize("@ss.hasPermi('monitor:server:list')") 80 @PreAuthorize("@ss.hasPermi('monitor:server:list')")
74 @GetMapping() 81 @GetMapping()
@@ -184,10 +191,44 @@ public class ServerController extends BaseController @@ -184,10 +191,44 @@ public class ServerController extends BaseController
184 { 191 {
185 day = DateUtils.getDate(); 192 day = DateUtils.getDate();
186 } 193 }
187 - List<Map<String,Object>> lsit = publicService.getObjectListBySQL("SELECT * FROM `fish_aquatic_public_opinion` WHERE release_time='"+day+" 00:00:00'"); 194 + 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");
188 return AjaxResult.success(lsit); 195 return AjaxResult.success(lsit);
189 } 196 }
190 197
  198 + @ApiOperation("获取当天水产鱼价新闻")
  199 + @GetMapping("/getYujiaInfo")
  200 + public AjaxResult getYujiaInfo(String day)
  201 + {
  202 + if(StringUtils.isEmpty(day))
  203 + {
  204 + day = DateUtils.getDate();
  205 + }
  206 + 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");
  207 + return AjaxResult.success(lsit);
  208 + }
  209 +
  210 + @ApiOperation("修改鱼价信息")
  211 + @PostMapping("/upYujiaInfo/{id}")
  212 + public AjaxResult upYujiaInfo(@PathVariable Integer id,@RequestBody String yujiaInfo)
  213 + {
  214 + FishAquaticPublicOpinion fishAquaticPublicOpinion = new FishAquaticPublicOpinion();
  215 + fishAquaticPublicOpinion.setId(id);
  216 + fishAquaticPublicOpinion.setFishPrice(yujiaInfo);
  217 + fishAquaticPublicOpinion.setAquaticType(4);
  218 + return AjaxResult.success(publicService.updateObject(fishAquaticPublicOpinion,"id"));
  219 + }
  220 +
  221 + @ApiOperation("修改鱼价信息为公众号信息")
  222 + @PostMapping("/upYujiaInfoToGzh/{id}")
  223 + public AjaxResult upYujiaInfoToGzh(@PathVariable Integer id)
  224 + {
  225 + FishAquaticPublicOpinion fishAquaticPublicOpinion = new FishAquaticPublicOpinion();
  226 + fishAquaticPublicOpinion.setId(id);
  227 + fishAquaticPublicOpinion.setAquaticType(2);
  228 + return AjaxResult.success(publicService.updateObject(fishAquaticPublicOpinion,"id"));
  229 + }
  230 +
  231 +
191 @ApiOperation("生成水产新闻海报") 232 @ApiOperation("生成水产新闻海报")
192 @PostMapping("/generateAquaticPublicOpinionPoster") 233 @PostMapping("/generateAquaticPublicOpinionPoster")
193 public AjaxResult generateAquaticPublicOpinionPoster(@RequestBody String str) 234 public AjaxResult generateAquaticPublicOpinionPoster(@RequestBody String str)
@@ -217,7 +258,38 @@ public class ServerController extends BaseController @@ -217,7 +258,38 @@ public class ServerController extends BaseController
217 258
218 } 259 }
219 260
  261 + @ApiOperation("生成当天水产价格海报")
  262 + @GetMapping("/generateNowJiagePoster")
  263 + public AjaxResult generateNowJiagePoster(String day)
  264 + {
  265 + if(StringUtils.isEmpty(day))
  266 + {
  267 + day = DateUtils.getDate();
  268 + }
  269 + 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");
  270 +
  271 + if (null == list || list.size()==0)
  272 + {
  273 + return AjaxResult.error("没有数据");
  274 + }
  275 + // 每个地区维护一个 Set 用于去重
  276 + Map<String, Set<String>> dedupMap = new HashMap<>();
  277 + // 按地区分组
  278 + Map<String, List<FishPrice>> grouped = new LinkedHashMap<>();
  279 + try {
  280 + for (Map<String, Object> item : list)
  281 + {
  282 + FishPriceTableWithLogo.addFishPrice(grouped, (String) item.get("fish_price"),dedupMap);
  283 + }
220 284
  285 + String savePath = FishPriceTableWithLogo.draw(DateUtils.parseDateToStr("yyyy年MM月dd日",DateUtils.parseDate(day)),grouped);
  286 + String imgurl = "https://lh.admin.yu2le.com/api/"+savePath.replace("uploadPath","profile");
  287 + return AjaxResult.success(imgurl);
  288 + } catch (Exception e) {
  289 + logger.error("生成价格海报错误",e);
  290 + return AjaxResult.error("生成价格海报错误"+e.getMessage());
  291 + }
  292 + }
221 293
222 @ApiOperation("获取服务器应用列表") 294 @ApiOperation("获取服务器应用列表")
223 @GetMapping("/getServiceAoolicationList") 295 @GetMapping("/getServiceAoolicationList")
@@ -388,6 +460,31 @@ public class ServerController extends BaseController @@ -388,6 +460,31 @@ public class ServerController extends BaseController
388 return AjaxResult.success(sysMonitorServerLog); 460 return AjaxResult.success(sysMonitorServerLog);
389 } 461 }
390 462
  463 + @ApiOperation("测试微信公众账号详细采集")
  464 + @GetMapping("/mpWeixinQqComGetInfo")
  465 + public String mpWeixinQqComGetInfo(String info_url)
  466 + {
  467 + String str = mpWeixinQqCom.getInfo(info_url);
  468 + return str;
  469 + }
  470 +
  471 + @ApiOperation("采集微信公众账号详情")
  472 + @GetMapping("/updateFishAquaticPublicOpinion")
  473 + public AjaxResult updateFishAquaticPublicOpinion(String id)
  474 + {
  475 + AsyncManager.me().execute(new TimerTask() {
  476 + @Override
  477 + public void run() {
  478 + FishAquaticPublicOpinion aquaticPublicOpinion = publicService.getObject(FishAquaticPublicOpinion.class,"id",id);
  479 + Map<String,Object> map = publicService.getObjectList(aquaticPublicOpinion,"id,info_url",null,null,0,0).get(0);
  480 + String text = mpWeixinQqCom.getInfo((String) map.get("info_url"));
  481 + mpWeixinQqCom.updateSaveInfo(text, (Integer) map.get("id"));
  482 + }
  483 + });
  484 +
  485 + return AjaxResult.success();
  486 + }
  487 +
391 public static void main(String[] args) { 488 public static void main(String[] args) {
392 String str = "[{\"content\":\"\\n中国水产学会2025年征集水产科普作品(图书、视频、活动),申报截止8月10日,优秀作品将获全国推广。\",\"title\":\"中国水产学会关于开展2025年度优秀水产科普作品征集活动的通知 2025-07-09\"},{\"content\":\"\\n2025年7月3日,长春举办水产期刊集群建设研讨会,70余位代表交流期刊发展及智慧渔业应用,推动行业创新提升。\",\"title\":\"水产期刊集群建设研讨会在长春召开 2025-07-09\"}]"; 489 String str = "[{\"content\":\"\\n中国水产学会2025年征集水产科普作品(图书、视频、活动),申报截止8月10日,优秀作品将获全国推广。\",\"title\":\"中国水产学会关于开展2025年度优秀水产科普作品征集活动的通知 2025-07-09\"},{\"content\":\"\\n2025年7月3日,长春举办水产期刊集群建设研讨会,70余位代表交流期刊发展及智慧渔业应用,推动行业创新提升。\",\"title\":\"水产期刊集群建设研讨会在长春召开 2025-07-09\"}]";
393 490
1 -# 项目相关配置 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  
  1 +# 项目相关配置 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

3.8 KB

@@ -620,6 +620,13 @@ @@ -620,6 +620,13 @@
620 <artifactId>jsoup</artifactId> 620 <artifactId>jsoup</artifactId>
621 <version>1.17.2</version> 621 <version>1.17.2</version>
622 </dependency> 622 </dependency>
  623 +
  624 + <!-- Tesseract OCR Java wrapper -->
  625 + <dependency>
  626 + <groupId>net.sourceforge.tess4j</groupId>
  627 + <artifactId>tess4j</artifactId>
  628 + <version>5.4.0</version>
  629 + </dependency>
623 </dependencies> 630 </dependencies>
624 631
625 632
不能预览此文件类型
不能预览此文件类型