This commit is contained in:
2026-06-12 11:48:17 +08:00
parent ee26bc6d7a
commit 8c93377659
45 changed files with 12786 additions and 1716 deletions
+3 -14
View File
@@ -15,19 +15,7 @@
票务API入口
</description>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>2.0.0-RC1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependencies>
<!-- spring-boot-devtools -->
<dependency>
@@ -205,6 +193,7 @@
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
<version>2.0.0-RC1</version>
</dependency>
</dependencies>
@@ -234,7 +223,7 @@
</configuration>
</plugin>
</plugins>
<finalName>${project.artifactId}</finalName>
<finalName>piaowu</finalName>
</build>
</project>
@@ -9,12 +9,12 @@ import org.springframework.scheduling.annotation.EnableAsync;
*/
@SpringBootApplication
@EnableAsync
public class RuoPiaoApplication
public class PiaoWuApplication
{
public static void main(String[] args)
{
// System.setProperty("spring.devtools.restart.enabled", "false");
SpringApplication.run(RuoPiaoApplication.class, args);
SpringApplication.run(PiaoWuApplication.class, args);
System.out.println(">>若票系统启动成功 :-) \n" +
" ____ ____ _\n" +
" | _ \\ _ _ ___ | _ \\ (_) __ _ ___\n" +
@@ -8,11 +8,11 @@ import org.springframework.boot.web.servlet.support.SpringBootServletInitializer
*
* @author ruoyi
*/
public class RuoPiaoServletInitializer extends SpringBootServletInitializer
public class PiaoWuServletInitializer extends SpringBootServletInitializer
{
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application)
{
return application.sources(RuoPiaoApplication.class);
return application.sources(PiaoWuApplication.class);
}
}
@@ -0,0 +1,204 @@
package com.ruoyi.piao.controller;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.piao.domain.ResponseDTO;
import com.ruoyi.piao.domain.entity.Change;
import com.ruoyi.piao.domain.entity.Order;
import com.ruoyi.piao.domain.entity.Refund;
import com.ruoyi.piao.service.ChangeService;
import com.ruoyi.piao.service.OrderService;
import com.ruoyi.piao.service.RefundService;
import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.util.*;
@RestController
@RequestMapping("/ai")
public class AIController {
private static List<Map<String, Object>> hotlineCache = null;
private static LocalDateTime hotlineCacheTime = null;
private static final long CACHE_DURATION_HOURS = 1;
@Resource
private OrderService orderService;
@Autowired
private RefundService refundService;
@Autowired
private ChangeService changeService;
@Resource
private ChatClient chatClient;
@Anonymous
@GetMapping("/hotline")
public ResponseDTO<List<Map<String, Object>>> hotline() {
if (hotlineCache != null && hotlineCacheTime != null
&& LocalDateTime.now().minusHours(CACHE_DURATION_HOURS).isBefore(hotlineCacheTime)) {
return ResponseDTO.ok(hotlineCache);
}
String prompt = "你是一个机票票务AI助手。请根据当前中国民航市场情况,生成当前最热门的国内航线列表。" +
"请直接返回JSON数组,不要加markdown代码块标记,格式为:[{\"route\": \"北京-上海\", \"reason\": \"商务航线,客流量大\", \"rank\": 1}]。" +
"返回8条,按热门程度排序,每条reason用中文简要描述热门原因。";
String aiResponse = chatClient.prompt()
.system("你是一个民航数据分析助手,只返回纯JSON数组,不要任何其他文字和markdown标记。")
.user(prompt)
.call()
.content();
if (aiResponse != null) {
aiResponse = aiResponse.trim();
if (aiResponse.startsWith("```")) {
aiResponse = aiResponse.replaceAll("```json\\s*", "").replaceAll("```\\s*", "").trim();
}
}
try {
if (aiResponse != null) {
List<Map<String, Object>> list = JSON.parseObject(aiResponse, new com.alibaba.fastjson.TypeReference<List<Map<String, Object>>>(){});
if (list != null && !list.isEmpty()) {
hotlineCache = list;
hotlineCacheTime = LocalDateTime.now();
return ResponseDTO.ok(list);
}
}
} catch (Exception ignored) {}
if (hotlineCache != null) {
return ResponseDTO.ok(hotlineCache);
}
List<Map<String, Object>> fallback = new ArrayList<>();
Map<String, Object> item = new HashMap<>();
item.put("route", "北京-上海");
item.put("reason", "热门商务航线");
item.put("rank", 1);
fallback.add(item);
item = new HashMap<>();
item.put("route", "广州-深圳");
item.put("reason", "热门商务航线");
item.put("rank", 2);
fallback.add(item);
return ResponseDTO.ok(fallback);
}
@Anonymous
@GetMapping("/chat")
public String chat(@RequestParam String message,
@RequestParam(required = false) String context) {
StringBuilder systemPrompt = new StringBuilder("你是一个机票票务系统的AI助手,帮助用户解答关于票务系统的问题。" +
"用中文回答,简洁清晰,必要时可以给出数据统计。");
if (StrUtil.isNotBlank(context)) {
JSONObject ctx = JSON.parseObject(context);
String route = ctx.getString("route");
String pageTitle = ctx.getString("title");
if (StrUtil.isNotBlank(pageTitle)) {
systemPrompt.append("\n用户当前在「").append(pageTitle).append("」页面。");
}
if (route != null) {
systemPrompt.append(injectPageData(route));
}
Object pageDataObj = ctx.get("data");
if (pageDataObj != null) {
String pageData = pageDataObj instanceof String ? (String) pageDataObj : pageDataObj.toString();
systemPrompt.append("\n页面自定义数据:").append(pageData);
}
}
return chatClient.prompt()
.system(systemPrompt.toString())
.user(message)
.call()
.content();
}
private String injectPageData(String route) {
StringBuilder sb = new StringBuilder();
try {
if (route.contains("/order") || route.contains("/sale")) {
long pendingCount = orderService.lambdaQuery().eq(Order::getStatus, "1").count();
long issuingCount = orderService.lambdaQuery().eq(Order::getStatus, "21").count();
if (pendingCount > 0) {
List<Order> urgent = orderService.lambdaQuery()
.eq(Order::getStatus, "1")
.orderByAsc(Order::getLimittime)
.last("LIMIT 5")
.list();
sb.append("\n待处理订单共").append(pendingCount).append("单,出票中").append(issuingCount).append("单。");
if (!urgent.isEmpty()) {
sb.append("\n最紧急的5单:");
for (Order o : urgent) {
sb.append("\n- 订单").append(o.getId())
.append(" 航班").append(o.getFlightNumber())
.append(" ").append(o.getDepCode()).append("").append(o.getArrCode())
.append(" 金额").append(o.getAmount())
.append(" 最晚出票").append(o.getLimittime());
}
}
}
}
if (route.contains("/order/list")) {
sb.append("\n可查询订单列表、状态、金额等信息。");
}
if (route.contains("/order/waiting") || route.contains("/order/urgent")) {
long waiting = orderService.lambdaQuery().eq(Order::getStatus, "1").count();
sb.append("\n待处理订单").append(waiting).append("单。");
}
if (route.contains("/order/mine")) {
sb.append("\n这是我的订单页面,可查询当前用户认领的订单。");
}
if (route.contains("/refund")) {
long refundPending = refundService.lambdaQuery().eq(Refund::getRefundStatus, 0).count();
sb.append("\n待退票共").append(refundPending).append("单。");
}
if (route.contains("/change")) {
long changePending = changeService.lambdaQuery().eq(Change::getChangeStatus, 0).count();
sb.append("\n待改签共").append(changePending).append("单。");
}
if (route.contains("/report")) {
sb.append("\n这是报表页面,可查询业绩、利润、结算等统计数据。");
}
if (route.contains("/config")) {
sb.append("\n这是配置管理页面,包含航空公司、舱位、渠道、客户、航线、规则等配置。");
}
if (route.contains("/sale/list")) {
sb.append("\n这是销售列表页面,可查询待处理销售订单。");
}
if (route.contains("/policy")) {
sb.append("\n这是政策管理页面,包含价格政策、OTA政策等。");
}
if (route.contains("/data/crawl")) {
sb.append("\n这是数据抓取页面,用于管理爬虫任务。");
}
if (route.equals("/home/index") || route.equals("/home") || route.equals("/index")) {
long orderCount = orderService.lambdaQuery().eq(Order::getStatus, "1").count();
long refundCount = refundService.lambdaQuery().eq(Refund::getRefundStatus, 0).count();
long changeCount = changeService.lambdaQuery().eq(Change::getChangeStatus, 0).count();
sb.append("\n首页概览:待处理订单").append(orderCount)
.append("单,待退票").append(refundCount)
.append("单,待改签").append(changeCount).append("单。");
}
} catch (Exception e) {
sb.append("\n(查询业务数据时出现异常)");
}
return sb.toString();
}
}
@@ -2,8 +2,11 @@ package com.ruoyi.piao.controller;
import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.piao.domain.ResponseDTO;
import com.ruoyi.piao.service.MailService;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@@ -14,4 +17,17 @@ public class HomeController {
return ResponseDTO.ok();
}
@Resource
MailService mailService;
@GetMapping("/mail")
@Anonymous
public ResponseDTO<String> sendMail(@RequestParam(defaultValue = "344656718@qq.com") String to,
@RequestParam(defaultValue = "测试邮件") String subject,
@RequestParam(defaultValue = "这是一封测试邮件") String content) {
mailService.sendEmail(to, subject, content);
return ResponseDTO.ok("邮件发送成功");
}
}
@@ -1,7 +1,6 @@
package com.ruoyi.piao.controller;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.common.annotation.Anonymous;
@@ -17,7 +16,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import java.util.HashMap;
import java.time.LocalDateTime;
import java.util.List;
@@ -191,112 +189,4 @@ public class MessageController {
}
}
@Resource
private ChatClient chatClient;
@GetMapping("/chat")
public String chat(@RequestParam String message,
@RequestParam(required = false) String context) {
StringBuilder systemPrompt = new StringBuilder("你是一个机票票务系统的AI助手,帮助用户解答关于票务系统的问题。" +
"用中文回答,简洁清晰,必要时可以给出数据统计。");
if (StrUtil.isNotBlank(context)) {
JSONObject ctx = JSON.parseObject(context);
String route = ctx.getString("route");
String pageTitle = ctx.getString("title");
if (StrUtil.isNotBlank(pageTitle)) {
systemPrompt.append("\n用户当前在「").append(pageTitle).append("」页面。");
}
if (route != null) {
systemPrompt.append(injectPageData(route));
}
Object pageDataObj = ctx.get("data");
if (pageDataObj != null) {
String pageData = pageDataObj instanceof String ? (String) pageDataObj : pageDataObj.toString();
systemPrompt.append("\n页面自定义数据:").append(pageData);
}
}
return chatClient.prompt()
.system(systemPrompt.toString())
.user(message)
.call()
.content();
}
private String injectPageData(String route) {
StringBuilder sb = new StringBuilder();
try {
if (route.contains("/order") || route.contains("/sale")) {
long pendingCount = orderService.lambdaQuery().eq(Order::getStatus, "1").count();
long issuingCount = orderService.lambdaQuery().eq(Order::getStatus, "21").count();
if (pendingCount > 0) {
List<Order> urgent = orderService.lambdaQuery()
.eq(Order::getStatus, "1")
.orderByAsc(Order::getLimittime)
.last("LIMIT 5")
.list();
sb.append("\n待处理订单共").append(pendingCount).append("单,出票中").append(issuingCount).append("单。");
if (!urgent.isEmpty()) {
sb.append("\n最紧急的5单:");
for (Order o : urgent) {
sb.append("\n- 订单").append(o.getId())
.append(" 航班").append(o.getFlightNumber())
.append(" ").append(o.getDepCode()).append("").append(o.getArrCode())
.append(" 金额").append(o.getAmount())
.append(" 最晚出票").append(o.getLimittime());
}
}
}
}
if (route.contains("/order/list")) {
sb.append("\n可查询订单列表、状态、金额等信息。");
}
if (route.contains("/order/waiting") || route.contains("/order/urgent")) {
long waiting = orderService.lambdaQuery().eq(Order::getStatus, "1").count();
sb.append("\n待处理订单").append(waiting).append("单。");
}
if (route.contains("/order/mine")) {
sb.append("\n这是我的订单页面,可查询当前用户认领的订单。");
}
if (route.contains("/refund")) {
long refundPending = refundService.lambdaQuery().eq(Refund::getRefundStatus, 0).count();
sb.append("\n待退票共").append(refundPending).append("单。");
}
if (route.contains("/change")) {
long changePending = changeService.lambdaQuery().eq(Change::getChangeStatus, 0).count();
sb.append("\n待改签共").append(changePending).append("单。");
}
if (route.contains("/report")) {
sb.append("\n这是报表页面,可查询业绩、利润、结算等统计数据。");
}
if (route.contains("/config")) {
sb.append("\n这是配置管理页面,包含航空公司、舱位、渠道、客户、航线、规则等配置。");
}
if (route.contains("/sale/list")) {
sb.append("\n这是销售列表页面,可查询待处理销售订单。");
}
if (route.contains("/policy")) {
sb.append("\n这是政策管理页面,包含价格政策、OTA政策等。");
}
if (route.contains("/data/crawl")) {
sb.append("\n这是数据抓取页面,用于管理爬虫任务。");
}
if (route.equals("/home/index") || route.equals("/home") || route.equals("/index")) {
long orderCount = orderService.lambdaQuery().eq(Order::getStatus, "1").count();
long refundCount = refundService.lambdaQuery().eq(Refund::getRefundStatus, 0).count();
long changeCount = changeService.lambdaQuery().eq(Change::getChangeStatus, 0).count();
sb.append("\n首页概览:待处理订单").append(orderCount)
.append("单,待退票").append(refundCount)
.append("单,待改签").append(changeCount).append("单。");
}
} catch (Exception e) {
sb.append("\n(查询业务数据时出现异常)");
}
return sb.toString();
}
}
@@ -114,7 +114,7 @@ public class SysConfigController extends BaseController
public AjaxResult editByKeyValue(@PathVariable String key,
@PathVariable String value)
{
SysConfig cfg = configService.selectConfigById(Long.parseLong(key));
SysConfig cfg = configService.selectConfigByConfigKey(key);
if (cfg == null) return error("参数未找到");
cfg.setUpdateBy(getUsername());
cfg.setConfigValue(value);
@@ -7,7 +7,7 @@ ruoyi:
# 版权年份
copyrightYear: 2025
# 上传文件路径 示例( Windows配置D:/ruoyi/uploadPathLinux配置 /home/ruoyi/uploadPath 或 docker 中的挂载目录)
profile: /app/upload/piao/
profile: /app/upload/piaowu/
# 获取ip地址开关
addressEnabled: false
# 验证码类型 math 数字计算 char 字符验证
@@ -16,7 +16,7 @@ ruoyi:
# 开发环境配置
server:
# 服务器的HTTP端口,默认为8080
port: 8080
port: 1024
servlet:
# 应用的访问路径
context-path: /
@@ -63,9 +63,9 @@ spring:
druid:
# 主库数据源 Abcd1234 P0i8a1o9@#()
master:
url: jdbc:mysql://mysql/piaoruo?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
url: jdbc:mysql://piaowumysql:3306/piaowu?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: P0i8a1o9@#()
password: P2i0a2o6w@u
# 从库数据源
slave:
# 从数据源开关/默认关闭
@@ -136,10 +136,10 @@ spring:
enabled: true
data:
redis:
host: redis
host: piaowuredis
port: 6379
database: 0
password: Rds123!@$
password: Rds234!@$
ai:
openai:
@@ -5,7 +5,7 @@ ruoyi:
# 版本
version: 3.9.2
# 版权年份
copyrightYear: 2025
copyrightYear: 2026
# 文件路径 示例( Windows配置D:/ruoyi/uploadPathLinux配置 /home/ruoyi/uploadPath
profile: D:/ruoyi/uploadPath
# 获取ip地址开关