正在显示
19 个修改的文件
包含
737 行增加
和
63 行删除
| @@ -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 | +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 |
logo.png
0 → 100644
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 |
tessdata/chi_sim.traineddata
0 → 100644
不能预览此文件类型
tessdata/eng.traineddata
0 → 100644
不能预览此文件类型
-
请 注册 或 登录 后发表评论