Swanky 1 week ago
commit b503d19ef6
  1. 7
      .gitignore
  2. 14
      data/magic-api/api/大屏数据/group.json
  3. 78
      data/magic-api/api/大屏数据/交易中心实时服务信息.ms
  4. 125
      data/magic-api/api/大屏数据/交易所牦牛成交数据.ms
  5. 217
      data/magic-api/api/大屏数据/地图迁徙数据.ms
  6. 116
      data/magic-api/api/大屏数据/实时交易统计.ms
  7. 171
      data/magic-api/api/大屏数据/市场实时监控.ms
  8. 77
      data/magic-api/api/大屏数据/市场实时监控播放配置.ms
  9. 95
      data/magic-api/api/大屏数据/市场环境监控.ms
  10. 116
      data/magic-api/api/大屏数据/活牛鲜肉价格趋势.ms
  11. 173
      data/magic-api/api/大屏数据/牦牛供应实时信息.ms
  12. 344
      data/magic-api/api/大屏数据/牦牛供应详情.ms
  13. 73
      data/magic-api/api/大屏数据/牦牛销售类型统计.ms
  14. 74
      data/magic-api/api/大屏数据/系统配置.ms
  15. 157
      data/magic-api/api/大屏数据/综合销售统计.ms
  16. 55
      data/magic-api/api/大屏数据/采购商户来源分析.ms
  17. 14
      data/magic-api/api/测试分组/group.json
  18. 25
      data/magic-api/api/测试分组/测试接口.ms
  19. 234
      magic-api-dashboard-interfaces.txt
  20. 59
      pom.xml
  21. 12
      src/main/java/com/livestock/trading/LivestockTradingApplication.java
  22. 33
      src/main/resources/application.yml
  23. 12
      src/test/java/com/livestock/trading/LivestockTradingApplicationTests.java

7
.gitignore vendored

@ -0,0 +1,7 @@
target/
.idea/
*.iml
.classpath
.project
.settings/
.DS_Store

@ -0,0 +1,14 @@
{
"properties" : { },
"id" : "f8e3d2c1b0a94e5f8a7b6c5d4e3f2a1",
"name" : "大屏数据",
"type" : "api",
"parentId" : "0",
"path" : "/dashboard",
"createTime" : 1780876800000,
"updateTime" : null,
"createBy" : "admin",
"updateBy" : null,
"paths" : [ ],
"options" : [ ]
}

@ -0,0 +1,78 @@
{
"properties" : { },
"id" : "6f7a8490b1c2d3e4f5a678904ab",
"script" : null,
"groupId" : "f8e3d2c1b0a94e5f8a7b6c5d4e3f2a1",
"name" : "交易中心实时服务信息",
"createTime" : 1780877300000,
"updateTime" : null,
"lock" : null,
"createBy" : "admin",
"updateBy" : "admin",
"path" : "/exchange-service-info",
"method" : "GET",
"parameters" : [ ],
"options" : [ ],
"requestBody" : "",
"headers" : [ ],
"paths" : [ ],
"responseBody" : null,
"description" : "交易中心实时服务信息:牦牛供应/待售/已售、剩余车位、进场车辆、供应商数量(只读)。牦牛=yak_trade_ear_tag_inventory;车位=yak_car_parking_zone;车辆=yak_car_record+yak_trade_entry_record;供应商=yak_sn_customer(SELLER)。",
"requestBodyDefinition" : null,
"responseBodyDefinition" : null
}
================================
// 只读查询,不修改任何数据
var sql = """
SELECT
(SELECT COUNT(*)
FROM yak_trade_ear_tag_inventory) AS total_supply,
(SELECT COUNT(*)
FROM yak_trade_ear_tag_inventory
WHERE status IN ('AVAILABLE', 'LOCKED')) AS for_sale_yaks,
(SELECT COUNT(*)
FROM yak_trade_ear_tag_inventory
WHERE status = 'SOLD') AS sold_yaks,
(SELECT COALESCE(SUM(total_capacity - current_count), 0)
FROM yak_car_parking_zone
WHERE status = 'NORMAL') AS remaining_parking,
(SELECT COUNT(*)
FROM (
SELECT plate_no AS vehicle_no
FROM yak_car_record
WHERE del_flag = '0'
AND entry_time >= CURRENT_DATE
UNION
SELECT vehicle_no
FROM yak_trade_entry_record
WHERE entered_at >= CURRENT_DATE
) v) AS entering_vehicles,
(SELECT COUNT(*)
FROM yak_sn_customer
WHERE del_flag = '0'
AND customer_type = 'SELLER') AS supplier_count
"""
var rows = db.select(sql)
var row = rows && rows.length > 0 ? rows[0] : null
if (!row) {
return {
totalSupply: 0,
forSaleYaks: 0,
soldYaks: 0,
remainingParking: 0,
enteringVehicles: 0,
supplierCount: 0
}
}
return {
totalSupply: row.totalSupply ? row.totalSupply : 0,
forSaleYaks: row.forSaleYaks ? row.forSaleYaks : 0,
soldYaks: row.soldYaks ? row.soldYaks : 0,
remainingParking: row.remainingParking ? row.remainingParking : 0,
enteringVehicles: row.enteringVehicles ? row.enteringVehicles : 0,
supplierCount: row.supplierCount ? row.supplierCount : 0
}

@ -0,0 +1,125 @@
{
"properties" : { },
"id" : "3c4d5e6f7a8490b1c2d3e4f5a678901",
"script" : null,
"groupId" : "f8e3d2c1b0a94e5f8a7b6c5d4e3f2a1",
"name" : "交易所牦牛成交数据",
"createTime" : 1780877000000,
"updateTime" : 1780817847278,
"lock" : null,
"createBy" : "admin",
"updateBy" : "admin",
"path" : "/yak-trading-data",
"method" : "GET",
"parameters" : [ {
"name" : "period",
"value" : null,
"description" : "时间维度:day(近6日)/week(近6周)/month(近6月),不传则返回全部",
"required" : false,
"dataType" : "String",
"type" : null,
"defaultValue" : null,
"validateType" : null,
"error" : null,
"expression" : null,
"children" : null
} ],
"options" : [ ],
"requestBody" : "",
"headers" : [ ],
"paths" : [ ],
"responseBody" : "{\n \"code\": -1,\n \"message\": \"系统内部出现错误\",\n \"data\": null,\n \"timestamp\": 1780817836665,\n \"executeTime\": 4\n}",
"description" : "交易所牦牛成交数据:近6个时间段的牦牛交易数量(头)与成交订单数量(单)趋势,数据源 yak_sn_order 已完成订单(只读查询)",
"requestBodyDefinition" : null,
"responseBodyDefinition" : null
}
================================
var buildSeries = (rows) => {
var labels = []
var yakTradingVolume = []
var orderCount = []
for (row in rows) {
labels.push(row.label)
yakTradingVolume.push(row.yakVolume ? row.yakVolume : 0)
orderCount.push(row.orderCount ? row.orderCount : 0)
}
return {
labels: labels,
yakTradingVolume: yakTradingVolume,
orderCount: orderCount
}
}
var daySql = """
SELECT
TO_CHAR(d.bucket_date, 'FMMM/FMDD') AS label,
COALESCE(SUM(o.quantity), 0) AS yak_volume,
COUNT(o.id) AS order_count
FROM (
SELECT (CURRENT_DATE - offs)::date AS bucket_date, offs
FROM generate_series(5, 0, -1) AS offs
) d
LEFT JOIN yak_sn_order o
ON o.del_flag = '0'
AND o.status = 'COMPLETED'
AND o.transaction_time >= d.bucket_date
AND o.transaction_time < d.bucket_date + INTERVAL '1 day'
GROUP BY d.bucket_date, d.offs
ORDER BY d.offs DESC
"""
var weekSql = """
SELECT
TO_CHAR(d.anchor_date, 'FMMM/FMDD') || '-' || TO_CHAR(d.anchor_date + 6, 'FMMM/FMDD') AS label,
COALESCE(SUM(o.quantity), 0) AS yak_volume,
COUNT(o.id) AS order_count
FROM (
SELECT (CURRENT_DATE - offs * 7)::date AS anchor_date, offs
FROM generate_series(5, 0, -1) AS offs
) d
LEFT JOIN yak_sn_order o
ON o.del_flag = '0'
AND o.status = 'COMPLETED'
AND o.transaction_time >= d.anchor_date
AND o.transaction_time < d.anchor_date + INTERVAL '7 days'
GROUP BY d.anchor_date, d.offs
ORDER BY d.offs DESC
"""
var monthSql = """
SELECT
TO_CHAR(d.month_start, 'YYYY/FMMM') AS label,
COALESCE(SUM(o.quantity), 0) AS yak_volume,
COUNT(o.id) AS order_count
FROM (
SELECT (date_trunc('month', CURRENT_DATE) - (offs || ' months')::interval)::date AS month_start, offs
FROM generate_series(5, 0, -1) AS offs
) d
LEFT JOIN yak_sn_order o
ON o.del_flag = '0'
AND o.status = 'COMPLETED'
AND o.transaction_time >= d.month_start
AND o.transaction_time < d.month_start + INTERVAL '1 month'
GROUP BY d.month_start, d.offs
ORDER BY d.offs DESC
"""
var dayRows = db.select(daySql)
var weekRows = db.select(weekSql)
var monthRows = db.select(monthSql)
var result = {
day: buildSeries(dayRows),
week: buildSeries(weekRows),
month: buildSeries(monthRows)
}
var p = period
if (p && result[p]) {
return result[p]
}
return result

@ -0,0 +1,217 @@
{
"properties" : { },
"id" : "9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4",
"script" : null,
"groupId" : "f8e3d2c1b0a94e5f8a7b6c5d4e3f2a1",
"name" : "地图迁徙数据",
"createTime" : 1780882000000,
"updateTime" : 1781360000000,
"lock" : null,
"createBy" : "admin",
"updateBy" : "admin",
"path" : "/map-trading-network",
"method" : "GET",
"parameters" : [ ],
"options" : [ ],
"requestBody" : "",
"headers" : [ ],
"paths" : [ ],
"responseBody" : null,
"description" : "中央地图迁徙数据:销售网络/源地供应/红原本地出栏,数据源 yak_sn_order + yak_sn_customer(只读)。枢纽名称需与系统配置 mapHub.name 一致。",
"requestBodyDefinition" : null,
"responseBodyDefinition" : null
}
================================
// 只读统计已完成订单;hubName 需与系统配置 mapHub.name 保持一致
var hubName = '红原县'
var getRowText = (row, camelKey, snakeKey) => {
if (row[camelKey]) {
return '' + row[camelKey]
}
if (row[snakeKey]) {
return '' + row[snakeKey]
}
return ''
}
var getRowNumber = (row, key) => {
var v = row[key]
if (v == null || v === '') {
return 0
}
return v
}
var normalizeCityKey = (name) => {
var text = '' + (name == null ? '' : name)
if (text.trim() == '') {
return '未知'
}
if (text.indexOf('北京') >= 0) { return '北京' }
if (text.indexOf('上海') >= 0) { return '上海' }
if (text.indexOf('天津') >= 0) { return '天津' }
if (text.indexOf('重庆') >= 0) { return '重庆' }
if (text.indexOf('成都') >= 0) { return '成都' }
if (text.indexOf('拉萨') >= 0) { return '拉萨' }
if (text.indexOf('西宁') >= 0) { return '西宁' }
if (text.indexOf('兰州') >= 0) { return '兰州' }
if (text.indexOf('西安') >= 0) { return '西安' }
if (text.indexOf('昆明') >= 0) { return '昆明' }
if (text.indexOf('贵阳') >= 0) { return '贵阳' }
if (text.indexOf('广州') >= 0) { return '广州' }
if (text.indexOf('深圳') >= 0) { return '深圳' }
if (text.indexOf('康定') >= 0) { return '康定' }
if (text.indexOf('香格里拉') >= 0) { return '香格里拉' }
if (text.indexOf('合作') >= 0) { return '合作' }
if (text.indexOf('甘南') >= 0) { return '甘南藏族自治州' }
if (text.indexOf('玉树') >= 0) { return '玉树' }
if (text.indexOf('甘孜') >= 0) { return '甘孜藏族自治州' }
if (text.indexOf('阿坝') >= 0) { return '阿坝县' }
if (text.indexOf('马尔康') >= 0) { return '马尔康' }
if (text.indexOf('理塘') >= 0) { return '理塘' }
if (text.indexOf('那曲') >= 0) { return '那曲' }
if (text.indexOf('果洛') >= 0) { return '果洛' }
if (text.indexOf('红原') >= 0) { return '红原县' }
return text
}
var buildDirectionalFlows = (rows, direction) => {
var flows = []
var valueMap = {}
var descMap = {}
if (!rows) {
return flows
}
for (row in rows) {
var rawName = getRowText(row, 'placeName', 'place_name')
var value = getRowNumber(row, 'value')
var sample = getRowText(row, 'samplePlace', 'sample_place')
if (sample == '') {
sample = rawName
}
if (value > 0) {
var cityKey = direction == 'local' ? rawName : normalizeCityKey(rawName)
if (cityKey != '未知') {
if (valueMap[cityKey] == null) {
valueMap[cityKey] = value
descMap[cityKey] = sample
if (direction == 'outflow') {
flows.push({
from: hubName,
to: cityKey,
value: value,
description: sample
})
} else {
flows.push({
from: cityKey,
to: hubName,
value: value,
description: sample
})
}
} else {
valueMap[cityKey] = valueMap[cityKey] + value
var descText = '' + descMap[cityKey]
if (descText.indexOf(sample) < 0 && sample != '') {
descMap[cityKey] = descText + ';' + sample
}
for (flow in flows) {
var flowCity = direction == 'outflow' ? flow.to : flow.from
if (flowCity == cityKey) {
flow.value = valueMap[cityKey]
flow.description = descMap[cityKey]
}
}
}
}
}
}
return flows
}
var outflowSql = """
SELECT
COALESCE(NULLIF(TRIM(c.region_name), ''), NULLIF(TRIM(o.destination_place), ''), '未知') AS place_name,
COALESCE(SUM(o.quantity), 0) AS value,
MAX(COALESCE(NULLIF(TRIM(c.region_name), ''), NULLIF(TRIM(o.destination_place), ''), '未知')) AS sample_place
FROM yak_sn_order o
LEFT JOIN yak_sn_customer c ON c.id = o.buyer_id AND c.del_flag = '0'
WHERE o.del_flag = '0'
AND o.status = 'COMPLETED'
GROUP BY COALESCE(NULLIF(TRIM(c.region_name), ''), NULLIF(TRIM(o.destination_place), ''), '未知')
HAVING COALESCE(SUM(o.quantity), 0) > 0
ORDER BY value DESC, place_name
"""
var inflowSql = """
SELECT
COALESCE(NULLIF(TRIM(c.region_name), ''), NULLIF(TRIM(o.origin_place), ''), '未知') AS place_name,
COALESCE(SUM(o.quantity), 0) AS value,
MAX(COALESCE(NULLIF(TRIM(c.region_name), ''), NULLIF(TRIM(o.origin_place), ''), '未知')) AS sample_place
FROM yak_sn_order o
LEFT JOIN yak_sn_customer c ON c.id = o.seller_id AND c.del_flag = '0'
WHERE o.del_flag = '0'
AND o.status = 'COMPLETED'
GROUP BY COALESCE(NULLIF(TRIM(c.region_name), ''), NULLIF(TRIM(o.origin_place), ''), '未知')
HAVING COALESCE(SUM(o.quantity), 0) > 0
ORDER BY value DESC, place_name
"""
var localSql = """
SELECT
COALESCE(
NULLIF((regexp_match(o.origin_place, '([^省市区县]+(?:镇|乡|街道))'))[1], ''),
NULLIF(TRIM(o.origin_place), ''),
'未知'
) AS place_name,
COALESCE(SUM(o.quantity), 0) AS value,
MAX(o.origin_place) AS sample_place
FROM yak_sn_order o
WHERE o.del_flag = '0'
AND o.status = 'COMPLETED'
AND position('红原' in o.origin_place) > 0
GROUP BY COALESCE(
NULLIF((regexp_match(o.origin_place, '([^省市区县]+(?:镇|乡|街道))'))[1], ''),
NULLIF(TRIM(o.origin_place), ''),
'未知'
)
HAVING COALESCE(SUM(o.quantity), 0) > 0
ORDER BY value DESC, place_name
"""
var outflowRows = db.select(outflowSql)
var inflowRows = db.select(inflowSql)
var localRows = db.select(localSql)
var outflowFlows = buildDirectionalFlows(outflowRows, 'outflow')
var inflowFlows = buildDirectionalFlows(inflowRows, 'inflow')
var localFlows = buildDirectionalFlows(localRows, 'local')
return {
tradingModes: {
outflow: {
title: '销售网络分布图',
description: '从' + hubName + '向全国各地输出牦牛的流向分布',
flows: outflowFlows
},
inflow: {
title: '源地供应分布图',
description: '全国各地向' + hubName + '供应牦牛的来源分布',
flows: inflowFlows
},
local: {
title: '红原出栏分布图',
description: hubName + '各乡镇牦牛出栏分布情况',
flows: localFlows
}
}
}

@ -0,0 +1,116 @@
{
"properties" : { },
"id" : "2b3c4d5e6f748590a1b2c3d4e5f6789",
"script" : null,
"groupId" : "f8e3d2c1b0a94e5f8a7b6c5d4e3f2a1",
"name" : "实时交易统计",
"createTime" : 1780876800000,
"updateTime" : 1780803706100,
"lock" : null,
"createBy" : "admin",
"updateBy" : "admin",
"path" : "/real-time-stats",
"method" : "GET",
"parameters" : [ {
"name" : "dimension",
"value" : null,
"description" : "时间维度:day(当天)/week(近一周)/month(近一月)/year(当年),不传则返回全部",
"required" : false,
"dataType" : "String",
"type" : null,
"defaultValue" : null,
"validateType" : null,
"error" : null,
"expression" : null,
"children" : null
} ],
"options" : [ ],
"requestBody" : "",
"headers" : [ ],
"paths" : [ ],
"responseBody" : null,
"description" : "实时交易统计:牦牛交易总量、订单交易总量、销售商户数量、采购商户数量,按当天/近一周/近一月/当年统计(只读查询)",
"requestBodyDefinition" : null,
"responseBodyDefinition" : null
}
================================
// 只读统计已完成订单,不修改任何数据
var sql = """
SELECT
dim,
COALESCE(SUM(quantity), 0) AS yak_total_volume,
COUNT(*) AS order_total_volume,
COUNT(DISTINCT seller_id) AS seller_count,
COUNT(DISTINCT buyer_id) AS buyer_count
FROM (
SELECT 'day' AS dim, quantity, seller_id, buyer_id
FROM yak_sn_order
WHERE del_flag = '0'
AND status = 'COMPLETED'
AND transaction_time >= CURRENT_DATE
AND transaction_time < CURRENT_DATE + INTERVAL '1 day'
UNION ALL
SELECT 'week' AS dim, quantity, seller_id, buyer_id
FROM yak_sn_order
WHERE del_flag = '0'
AND status = 'COMPLETED'
AND transaction_time >= CURRENT_DATE - INTERVAL '6 days'
AND transaction_time < CURRENT_DATE + INTERVAL '1 day'
UNION ALL
SELECT 'month' AS dim, quantity, seller_id, buyer_id
FROM yak_sn_order
WHERE del_flag = '0'
AND status = 'COMPLETED'
AND transaction_time >= CURRENT_DATE - INTERVAL '29 days'
AND transaction_time < CURRENT_DATE + INTERVAL '1 day'
UNION ALL
SELECT 'year' AS dim, quantity, seller_id, buyer_id
FROM yak_sn_order
WHERE del_flag = '0'
AND status = 'COMPLETED'
AND transaction_time >= DATE_TRUNC('year', CURRENT_DATE)
AND transaction_time < CURRENT_DATE + INTERVAL '1 day'
) t
GROUP BY dim
ORDER BY dim
"""
var rows = db.select(sql)
var createEmptyStats = () => {
return {
yakTotalVolume: 0,
orderTotalVolume: 0,
sellerCount: 0,
buyerCount: 0
}
}
var result = {
day: createEmptyStats(),
week: createEmptyStats(),
month: createEmptyStats(),
year: createEmptyStats()
}
for (row in rows) {
result[row.dim] = {
yakTotalVolume: row.yakTotalVolume,
orderTotalVolume: row.orderTotalVolume,
sellerCount: row.sellerCount,
buyerCount: row.buyerCount
}
}
var dim = dimension
if (dim && result[dim]) {
return result[dim]
}
return result

@ -0,0 +1,171 @@
{
"properties" : { },
"id" : "c3d4e5f678901234567890abcdef0123",
"script" : null,
"groupId" : "f8e3d2c1b0a94e5f8a7b6c5d4e3f2a1",
"name" : "市场实时监控",
"createTime" : 1781199600000,
"updateTime" : null,
"lock" : null,
"createBy" : "admin",
"updateBy" : "admin",
"path" : "/market-realtime-monitor",
"method" : "GET",
"parameters" : [ ],
"options" : [ ],
"requestBody" : "",
"headers" : [ ],
"paths" : [ ],
"responseBody" : null,
"description" : "市场实时监控:视频监控设备列表,数据源 iot_device_video + iot_device_video_data 最新一条(只读)。",
"requestBodyDefinition" : null,
"responseBodyDefinition" : null
}
================================
// 只读查询,不修改任何数据
var sql = """
SELECT
v.id,
v.name,
v.number,
v.location,
v.address,
v.status,
v.play_url,
v.hd_play_url,
v.preview_img_url,
v.img,
v.channel_number,
v."index" AS sort_index,
v.last_capture_time,
d.stream_url AS latest_stream_url,
d.snapshot_url AS latest_snapshot_url,
d.online_status AS latest_online_status,
d.stream_status AS latest_stream_status,
d.fault_code AS latest_fault_code
FROM iot_device_video v
LEFT JOIN LATERAL (
SELECT
stream_url,
snapshot_url,
online_status,
stream_status,
fault_code
FROM iot_device_video_data
WHERE device_id = v.id
ORDER BY collect_time DESC NULLS LAST
LIMIT 1
) d ON true
WHERE COALESCE(v.del_flag, '0') = '0'
AND COALESCE(v.is_show, true) = true
ORDER BY v."index" NULLS LAST, v.name
"""
var rows = db.select(sql)
var cameras = []
var pickText = (row, camelKey, snakeKey) => {
if (!row) {
return ''
}
if (row[camelKey]) {
return row[camelKey]
}
if (row[snakeKey]) {
return row[snakeKey]
}
return ''
}
var toUpperText = (text) => {
if (!text) {
return ''
}
return ('' + text).toUpperCase()
}
var mapStatus = (row) => {
var deviceStatus = pickText(row, 'status', 'status')
var onlineStatus = pickText(row, 'latestOnlineStatus', 'latest_online_status')
var faultCode = pickText(row, 'latestFaultCode', 'latest_fault_code')
var deviceUpper = toUpperText(deviceStatus)
if (deviceUpper === 'OFFLINE') {
return 'offline'
}
if (deviceUpper === 'ERROR' || deviceUpper === 'FAULT') {
return 'error'
}
if (faultCode) {
return 'error'
}
if (onlineStatus) {
var onlineUpper = toUpperText(onlineStatus)
if (onlineUpper === 'OFFLINE') {
return 'offline'
}
if (onlineUpper === 'ERROR' || onlineUpper === 'FAULT') {
return 'error'
}
}
return 'online'
}
var pickStreamUrl = (row) => {
var hd = pickText(row, 'hdPlayUrl', 'hd_play_url')
if (hd) {
return hd
}
var play = pickText(row, 'playUrl', 'play_url')
if (play) {
return play
}
return pickText(row, 'latestStreamUrl', 'latest_stream_url')
}
var pickHdStreamUrl = (row) => {
return pickText(row, 'hdPlayUrl', 'hd_play_url')
}
var pickPlayUrl = (row) => {
return pickText(row, 'playUrl', 'play_url')
}
var pickPreviewUrl = (row) => {
var preview = pickText(row, 'previewImgUrl', 'preview_img_url')
if (preview) {
return preview
}
var img = pickText(row, 'img', 'img')
if (img) {
return img
}
return pickText(row, 'latestSnapshotUrl', 'latest_snapshot_url')
}
if (rows && rows.length > 0) {
for (row in rows) {
cameras.push({
id: pickText(row, 'id', 'id'),
name: pickText(row, 'name', 'name'),
number: pickText(row, 'number', 'number'),
location: pickText(row, 'location', 'location'),
address: pickText(row, 'address', 'address'),
channelNumber: row.channelNumber != null ? row.channelNumber : row.channel_number,
sortIndex: row.sortIndex != null ? row.sortIndex : row.sort_index,
status: mapStatus(row),
resolution: '1920x1080',
preview: pickPreviewUrl(row),
hdStreamUrl: pickHdStreamUrl(row),
playUrl: pickPlayUrl(row),
streamUrl: pickStreamUrl(row)
})
}
}
return {
pageSize: 2,
autoPlayInterval: 10000,
cameras: cameras
}

@ -0,0 +1,77 @@
{
"properties" : { },
"id" : "d4e5f678901234567890abcdef012345",
"script" : null,
"groupId" : "f8e3d2c1b0a94e5f8a7b6c5d4e3f2a1",
"name" : "市场实时监控播放配置",
"createTime" : 1781203200000,
"updateTime" : null,
"lock" : null,
"createBy" : "admin",
"updateBy" : "admin",
"path" : "/market-realtime-player-config",
"method" : "GET",
"parameters" : [ ],
"options" : [ ],
"requestBody" : "",
"headers" : [ ],
"paths" : [ ],
"responseBody" : null,
"description" : "市场实时监控播放配置:直播流格式识别规则与播放器参数(HLS/FLV/MP4 等)。",
"requestBodyDefinition" : null,
"responseBodyDefinition" : null
}
================================
// 直播流地址格式配置,供前端 hls.js / flv.js 自动选型
return {
defaultProtocol: 'hls',
urlPriority: ['hdStreamUrl', 'streamUrl', 'playUrl'],
formatRules: [
{
type: 'hls',
match: '.m3u8',
library: 'hls.js',
description: 'HLS 直播(萤石 open.ys7.com 等)'
},
{
type: 'flv',
match: '.flv',
library: 'flv.js',
description: 'HTTP-FLV 直播(萤石 rtmp*open.ys7.com 等)'
},
{
type: 'mp4',
match: '.mp4',
library: 'native',
description: 'MP4 点播/回放'
},
{
type: 'webm',
match: '.webm',
library: 'native',
description: 'WebM 点播'
}
],
playerOptions: {
hls: {
enableWorker: true,
lowLatencyMode: true
},
flv: {
isLive: true,
hasAudio: true,
hasVideo: true
},
flvMedia: {
enableWorker: true,
enableStashBuffer: false
}
},
ys7Templates: {
hdHls: 'https://open.ys7.com/v3/openlive/{deviceSerial}_{channelNo}_1.m3u8',
hls: 'https://open.ys7.com/v3/openlive/{deviceSerial}_{channelNo}_2.m3u8',
hdFlv: 'https://rtmp12open.ys7.com:9188/v3/openlive/{deviceSerial}_{channelNo}_1.flv',
flv: 'https://rtmp12open.ys7.com:9188/v3/openlive/{deviceSerial}_{channelNo}_2.flv'
}
}

@ -0,0 +1,95 @@
{
"properties" : { },
"id" : "b2c3d4e5f678901234567890abcdef01",
"script" : null,
"groupId" : "f8e3d2c1b0a94e5f8a7b6c5d4e3f2a1",
"name" : "市场环境监控",
"createTime" : 1781196000000,
"updateTime" : null,
"lock" : null,
"createBy" : "admin",
"updateBy" : "admin",
"path" : "/market-environment",
"method" : "GET",
"parameters" : [ ],
"options" : [ ],
"requestBody" : "",
"headers" : [ ],
"paths" : [ ],
"responseBody" : null,
"description" : "市场环境监控:温湿度(iot_device_env_data)+ 气象(iot_device_weather_data)最新一条,只读。",
"requestBodyDefinition" : null,
"responseBodyDefinition" : null
}
================================
// 只读查询,不修改任何数据
var envSql = """
SELECT
temperature,
humidity,
to_char(collect_time, 'YYYY-MM-DD HH24:MI:SS') AS collect_time
FROM iot_device_env_data
ORDER BY collect_time DESC NULLS LAST
LIMIT 1
"""
var weatherSql = """
SELECT
air_pressure,
pm25,
pm10,
uv_index,
rainfall,
wind_speed,
wind_direction,
to_char(collect_time, 'YYYY-MM-DD HH24:MI:SS') AS collect_time
FROM iot_device_weather_data
ORDER BY collect_time DESC NULLS LAST
LIMIT 1
"""
var envRows = db.select(envSql)
var weatherRows = db.select(weatherSql)
var env = envRows && envRows.length > 0 ? envRows[0] : null
var weather = weatherRows && weatherRows.length > 0 ? weatherRows[0] : null
var pickNum = (row, camelKey, snakeKey) => {
if (!row) {
return null
}
if (row[camelKey] != null) {
return row[camelKey]
}
if (row[snakeKey] != null) {
return row[snakeKey]
}
return null
}
var pickText = (row, camelKey, snakeKey) => {
if (!row) {
return ''
}
if (row[camelKey]) {
return row[camelKey]
}
if (row[snakeKey]) {
return row[snakeKey]
}
return ''
}
return {
temperature: pickNum(env, 'temperature', 'temperature'),
humidity: pickNum(env, 'humidity', 'humidity'),
envCollectTime: pickText(env, 'collectTime', 'collect_time'),
airPressure: pickNum(weather, 'airPressure', 'air_pressure'),
pm25: pickNum(weather, 'pm25', 'pm25'),
pm10: pickNum(weather, 'pm10', 'pm10'),
uvIndex: pickNum(weather, 'uvIndex', 'uv_index'),
rainfall: pickNum(weather, 'rainfall', 'rainfall'),
windSpeed: pickNum(weather, 'windSpeed', 'wind_speed'),
windDirection: pickNum(weather, 'windDirection', 'wind_direction'),
weatherCollectTime: pickText(weather, 'collectTime', 'collect_time')
}

@ -0,0 +1,116 @@
{
"properties" : { },
"id" : "5e6f7a8490b1c2d3e4f5a678903",
"script" : null,
"groupId" : "f8e3d2c1b0a94e5f8a7b6c5d4e3f2a1",
"name" : "活牛鲜肉价格趋势",
"createTime" : 1780877200000,
"updateTime" : null,
"lock" : null,
"createBy" : "admin",
"updateBy" : "admin",
"path" : "/price-trend",
"method" : "GET",
"parameters" : [ ],
"options" : [ ],
"requestBody" : "",
"headers" : [ ],
"paths" : [ ],
"responseBody" : null,
"description" : "活牛/鲜肉价格趋势:近8个月市场采集均价。数据源 yak_trade_market_price,活牛=LIVE_YAK,鲜肉=FRESH_MEAT,直接使用 price 字段及 unit 单位,不做重量折算。",
"requestBodyDefinition" : null,
"responseBodyDefinition" : null
}
================================
// 只读查询,不修改任何数据
// 数据源:yak_trade_market_price(市场采集价格,已含 price + unit)
// price_type: LIVE_YAK=活牛, FRESH_MEAT=鲜肉/牛肉
var monthBucketSql = """
SELECT
d.month_start,
d.offs,
TO_CHAR(d.month_start, 'FMMM') || '月' AS label
FROM (
SELECT
(date_trunc('month', CURRENT_DATE) - (offs || ' months')::interval)::date AS month_start,
offs
FROM generate_series(7, 0, -1) AS offs
) d
ORDER BY d.offs DESC
"""
var livePriceSql = """
SELECT
date_trunc('month', p.price_date)::date AS month_start,
ROUND(AVG(p.price)::numeric, 2) AS live_cattle_price
FROM yak_trade_market_price p
WHERE p.price_type = 'LIVE_YAK'
AND p.price_date >= date_trunc('month', CURRENT_DATE) - INTERVAL '7 months'
GROUP BY date_trunc('month', p.price_date)::date
"""
var beefPriceSql = """
SELECT
date_trunc('month', p.price_date)::date AS month_start,
ROUND(AVG(p.price)::numeric, 2) AS beef_price
FROM yak_trade_market_price p
WHERE p.price_type = 'FRESH_MEAT'
AND p.price_date >= date_trunc('month', CURRENT_DATE) - INTERVAL '7 months'
GROUP BY date_trunc('month', p.price_date)::date
"""
var unitSql = """
SELECT
MAX(CASE WHEN price_type = 'LIVE_YAK' THEN unit END) AS live_unit,
MAX(CASE WHEN price_type = 'FRESH_MEAT' THEN unit END) AS beef_unit
FROM yak_trade_market_price
WHERE price_type IN ('LIVE_YAK', 'FRESH_MEAT')
"""
var buckets = db.select(monthBucketSql)
var liveRows = db.select(livePriceSql)
var beefRows = db.select(beefPriceSql)
var unitRows = db.select(unitSql)
var liveMap = {}
var beefMap = {}
for (row in liveRows) {
liveMap[row.monthStart] = row.liveCattlePrice
}
for (row in beefRows) {
beefMap[row.monthStart] = row.beefPrice
}
var liveUnit = '元/公斤'
var beefUnit = '元/公斤'
if (unitRows && unitRows.length > 0) {
if (unitRows[0].liveUnit) {
liveUnit = unitRows[0].liveUnit
}
if (unitRows[0].beefUnit) {
beefUnit = unitRows[0].beefUnit
}
}
var labels = []
var liveCattlePrice = []
var beefPrice = []
for (bucket in buckets) {
var monthKey = bucket.monthStart
labels.push(bucket.label)
liveCattlePrice.push(liveMap[monthKey] != null ? liveMap[monthKey] : null)
beefPrice.push(beefMap[monthKey] != null ? beefMap[monthKey] : null)
}
return {
labels: labels,
liveCattlePrice: liveCattlePrice,
beefPrice: beefPrice,
unit: liveUnit,
liveUnit: liveUnit,
beefUnit: beefUnit
}

@ -0,0 +1,173 @@
{
"properties" : { },
"id" : "a1b2c3d4e5f6478990abcdef12345678",
"script" : null,
"groupId" : "f8e3d2c1b0a94e5f8a7b6c5d4e3f2a1",
"name" : "牦牛供应实时信息",
"createTime" : 1780882000000,
"updateTime" : null,
"lock" : null,
"createBy" : "admin",
"updateBy" : "admin",
"path" : "/yak-supply-realtime-info",
"method" : "GET",
"parameters" : [ {
"name" : "pageNo",
"value" : "1",
"description" : "页码,从 1 开始(勿用 page,与 magic-api 内置分页参数冲突)",
"required" : false,
"dataType" : "Integer",
"type" : null,
"defaultValue" : "1",
"validateType" : null,
"error" : null,
"expression" : null,
"children" : null
}, {
"name" : "pageSize",
"value" : "5",
"description" : "每页条数",
"required" : false,
"dataType" : "Integer",
"type" : null,
"defaultValue" : "5",
"validateType" : null,
"error" : null,
"expression" : null,
"children" : null
} ],
"options" : [ ],
"requestBody" : "",
"headers" : [ ],
"paths" : [ ],
"responseBody" : null,
"description" : "牦牛供应实时信息:卖家进场登记列表与汇总,数据源 yak_trade_entry_record + 耳标库存(只读)。",
"requestBodyDefinition" : null,
"responseBodyDefinition" : null
}
================================
// 只读查询,不修改任何数据
// page 为 magic-api 内置分页变量,页码参数使用 pageNo
var pageNum = 1
var sizeNum = 5
if (pageNo) {
pageNum = pageNo
}
if (pageSize) {
sizeNum = pageSize
}
if (pageNum < 1) {
pageNum = 1
}
if (sizeNum < 1) {
sizeNum = 5
}
if (sizeNum > 50) {
sizeNum = 50
}
var offset = (pageNum - 1) * sizeNum
var summarySql = """
SELECT
COUNT(*) FILTER (WHERE entered_at >= CURRENT_DATE) AS today_entries,
COALESCE(SUM(yak_count), 0) AS total_yaks,
COUNT(*) FILTER (WHERE sold_count > 0 AND sold_count < yak_count) AS trading_count,
COUNT(*) AS total_records
FROM (
SELECT
e.entered_at,
COALESCE(NULLIF(inv.yak_count, 0), et.yak_count, 0) AS yak_count,
COALESCE(inv.sold_count, 0) AS sold_count
FROM yak_trade_entry_record e
LEFT JOIN (
SELECT
seller_entry_record_id,
COUNT(*) AS yak_count,
COUNT(*) FILTER (WHERE status = 'SOLD') AS sold_count
FROM yak_trade_ear_tag_inventory
GROUP BY seller_entry_record_id
) inv ON inv.seller_entry_record_id = e.id
LEFT JOIN (
SELECT entry_record_id, COUNT(*) AS yak_count
FROM yak_trade_entry_record_ear_tag
GROUP BY entry_record_id
) et ON et.entry_record_id = e.id
WHERE e.entry_type = 'SELLER'
) t
"""
var listSql = """
SELECT
e.id,
e.name,
e.vehicle_no,
e.phone,
COALESCE(c.region_name, c.address, '') AS origin,
COALESCE(e.quarantine_certificate_no, '') AS quarantine_no,
to_char(e.entered_at, 'YYYY-MM-DD HH24:MI') AS entry_time,
COALESCE(NULLIF(inv.yak_count, 0), et.yak_count, 0) AS yak_count,
COALESCE(inv.sold_count, 0) AS sold_count,
CASE
WHEN COALESCE(NULLIF(inv.yak_count, 0), et.yak_count, 0) = 0 THEN 0
ELSE ROUND(
COALESCE(inv.sold_count, 0) * 100.0
/ COALESCE(NULLIF(inv.yak_count, 0), et.yak_count, 0)
)::int
END AS progress
FROM yak_trade_entry_record e
LEFT JOIN (
SELECT
seller_entry_record_id,
COUNT(*) AS yak_count,
COUNT(*) FILTER (WHERE status = 'SOLD') AS sold_count
FROM yak_trade_ear_tag_inventory
GROUP BY seller_entry_record_id
) inv ON inv.seller_entry_record_id = e.id
LEFT JOIN (
SELECT entry_record_id, COUNT(*) AS yak_count
FROM yak_trade_entry_record_ear_tag
GROUP BY entry_record_id
) et ON et.entry_record_id = e.id
LEFT JOIN yak_sn_customer c
ON c.id = e.party_id
AND c.del_flag = '0'
WHERE e.entry_type = 'SELLER'
ORDER BY e.entered_at DESC
LIMIT #{sizeNum} OFFSET #{offset}
"""
var summaryRows = db.select(summarySql)
var summaryRow = summaryRows && summaryRows.length > 0 ? summaryRows[0] : null
var rows = db.select(listSql)
var list = []
for (row in rows) {
var yakCount = row.yakCount ? row.yakCount : (row.yak_count ? row.yak_count : 0)
var soldCount = row.soldCount ? row.soldCount : (row.sold_count ? row.sold_count : 0)
var progress = row.progress ? row.progress : 0
list.push({
id: row.id,
name: row.name ? row.name : '',
licensePlate: row.vehicleNo ? row.vehicleNo : (row.vehicle_no ? row.vehicle_no : ''),
yakCount: yakCount,
contact: row.phone ? row.phone : '',
origin: row.origin ? row.origin : '',
quarantineNo: row.quarantineNo ? row.quarantineNo : (row.quarantine_no ? row.quarantine_no : ''),
entryTime: row.entryTime ? row.entryTime : (row.entry_time ? row.entry_time : ''),
progress: progress,
tradedCount: soldCount,
pendingCount: yakCount - soldCount
})
}
return {
summary: {
todayEntries: summaryRow && summaryRow.todayEntries ? summaryRow.todayEntries : (summaryRow && summaryRow.today_entries ? summaryRow.today_entries : 0),
totalYaks: summaryRow && summaryRow.totalYaks ? summaryRow.totalYaks : (summaryRow && summaryRow.total_yaks ? summaryRow.total_yaks : 0),
tradingCount: summaryRow && summaryRow.tradingCount ? summaryRow.tradingCount : (summaryRow && summaryRow.trading_count ? summaryRow.trading_count : 0)
},
total: summaryRow && summaryRow.totalRecords ? summaryRow.totalRecords : (summaryRow && summaryRow.total_records ? summaryRow.total_records : 0),
list: list
}

@ -0,0 +1,344 @@
{
"properties" : { },
"id" : "b2c3d4e5f6a7488990abcdef123456789",
"script" : null,
"groupId" : "f8e3d2c1b0a94e5f8a7b6c5d4e3f2a1",
"name" : "牦牛供应详情",
"createTime" : 1780882100000,
"updateTime" : null,
"lock" : null,
"createBy" : "admin",
"updateBy" : "admin",
"path" : "/yak-supply-detail",
"method" : "GET",
"parameters" : [ {
"name" : "id",
"value" : null,
"description" : "进场登记 ID(yak_trade_entry_record.id)",
"required" : true,
"dataType" : "String",
"type" : null,
"defaultValue" : null,
"validateType" : null,
"error" : null,
"expression" : null,
"children" : null
} ],
"options" : [ ],
"requestBody" : "",
"headers" : [ ],
"paths" : [ ],
"responseBody" : null,
"description" : "牦牛供应单条详情:yak_trade_entry_record + 耳标库存 + 订单/称重 + 证件图(只读)。",
"requestBodyDefinition" : null,
"responseBodyDefinition" : null
}
================================
// 只读查询,不修改任何数据
if (!id) {
exit 400, '缺少参数 id'
}
var pickUrl = (value) => {
if (!value) {
return ''
}
var text = (value + '').trim()
if (!text) {
return ''
}
if (text.indexOf('data:') === 0) {
return text
}
if (text.indexOf('http://localhost:8080') === 0) {
return text.substring('http://localhost:8080'.length)
}
if (text.indexOf('http://127.0.0.1:8080') === 0) {
return text.substring('http://127.0.0.1:8080'.length)
}
if (text.indexOf('http') === 0 || text.indexOf('/') === 0) {
return text
}
if (text.indexOf('profile/') === 0) {
return '/' + text
}
if (text.match(/^\d{4}\/\d{2}\/\d{2}\//)) {
return '/profile/' + text
}
return ''
}
var baseSql = """
SELECT
e.id,
e.name,
e.vehicle_no,
e.phone,
e.party_id,
COALESCE(c.region_name, c.address, '') AS origin,
COALESCE(e.quarantine_certificate_no, '') AS quarantine_no,
to_char(e.entered_at, 'YYYY-MM-DD HH24:MI') AS entry_time,
COALESCE(NULLIF(inv.yak_count, 0), et.yak_count, 0) AS yak_count,
COALESCE(inv.sold_count, 0) AS sold_count,
e.entry_photo_id,
cert.certificate_image_file_id,
cr.entry_image AS car_entry_image,
entry_oss.url AS entry_oss_url,
cert_oss.url AS cert_oss_url,
COALESCE(ord.order_count, 0) AS order_count
FROM yak_trade_entry_record e
LEFT JOIN (
SELECT
seller_entry_record_id,
COUNT(*) AS yak_count,
COUNT(*) FILTER (WHERE status = 'SOLD') AS sold_count
FROM yak_trade_ear_tag_inventory
GROUP BY seller_entry_record_id
) inv ON inv.seller_entry_record_id = e.id
LEFT JOIN (
SELECT entry_record_id, COUNT(*) AS yak_count
FROM yak_trade_entry_record_ear_tag
GROUP BY entry_record_id
) et ON et.entry_record_id = e.id
LEFT JOIN yak_sn_customer c
ON c.id = e.party_id
AND c.del_flag = '0'
LEFT JOIN LATERAL (
SELECT certificate_image_file_id
FROM yak_trade_entry_record_ear_tag t
WHERE t.entry_record_id = e.id
AND t.certificate_image_file_id IS NOT NULL
AND TRIM(t.certificate_image_file_id) <> ''
ORDER BY t.create_time
LIMIT 1
) cert ON TRUE
LEFT JOIN LATERAL (
SELECT entry_image
FROM yak_car_record cr
WHERE cr.del_flag = '0'
AND cr.plate_no = e.vehicle_no
ORDER BY cr.entry_time DESC NULLS LAST
LIMIT 1
) cr ON TRUE
LEFT JOIN yak_trade_quarantine_certificate qc
ON qc.certificate_no = e.quarantine_certificate_no
LEFT JOIN LATERAL (
SELECT o.url
FROM sys_oss o
WHERE e.entry_photo_id IS NOT NULL
AND TRIM(e.entry_photo_id) <> ''
AND (
o.oss_id::text = e.entry_photo_id
OR o.ext1 = e.entry_photo_id
OR o.file_name ILIKE '%' || e.entry_photo_id || '%'
OR o.original_name ILIKE '%' || e.entry_photo_id || '%'
OR o.url ILIKE '%' || e.entry_photo_id || '%'
)
ORDER BY
CASE
WHEN o.oss_id::text = e.entry_photo_id THEN 0
WHEN o.ext1 = e.entry_photo_id THEN 1
ELSE 2
END
LIMIT 1
) entry_oss ON TRUE
LEFT JOIN LATERAL (
SELECT o.url
FROM sys_oss o
WHERE (
cert.certificate_image_file_id IS NOT NULL
AND TRIM(cert.certificate_image_file_id) <> ''
AND (
o.oss_id::text = cert.certificate_image_file_id
OR o.ext1 = cert.certificate_image_file_id
OR o.file_name ILIKE '%' || cert.certificate_image_file_id || '%'
OR o.original_name ILIKE '%' || cert.certificate_image_file_id || '%'
OR o.url ILIKE '%' || cert.certificate_image_file_id || '%'
)
)
OR (
qc.image_file_id IS NOT NULL
AND TRIM(qc.image_file_id) <> ''
AND (
o.oss_id::text = qc.image_file_id
OR o.ext1 = qc.image_file_id
OR o.file_name ILIKE '%' || qc.image_file_id || '%'
OR o.original_name ILIKE '%' || qc.image_file_id || '%'
OR o.url ILIKE '%' || qc.image_file_id || '%'
)
)
ORDER BY
CASE
WHEN cert.certificate_image_file_id IS NOT NULL
AND o.oss_id::text = cert.certificate_image_file_id THEN 0
WHEN qc.image_file_id IS NOT NULL
AND o.oss_id::text = qc.image_file_id THEN 1
ELSE 2
END
LIMIT 1
) cert_oss ON TRUE
LEFT JOIN (
SELECT
inv.seller_entry_record_id,
COUNT(DISTINCT o.id) AS order_count
FROM yak_trade_ear_tag_inventory inv
JOIN yak_trade_order_item i ON i.ear_tag_no = inv.ear_tag_no
JOIN yak_trade_order o ON o.id = i.order_id
WHERE o.status <> 'CANCELLED'
GROUP BY inv.seller_entry_record_id
) ord ON ord.seller_entry_record_id = e.id
WHERE e.id = #{id}
AND e.entry_type = 'SELLER'
"""
var orderSql = """
SELECT
o.id,
COALESCE(bc.customer_name, be.name, o.buyer_id, '') AS buyer,
COALESCE(sc.customer_name, e.name, o.seller_id, '') AS seller,
to_char(o.created_at, 'YYYY-MM-DD HH24:MI') AS trade_time,
COALESCE(SUM(COALESCE(i.quantity, 1)), 0) AS quantity,
COALESCE(SUM(i.weight), 0) AS weight,
MAX(w.photo_id) AS weight_photo_id,
MAX(wo.url) AS weight_photo_url
FROM yak_trade_entry_record e
JOIN yak_trade_ear_tag_inventory inv
ON inv.seller_entry_record_id = e.id
JOIN yak_trade_order_item i
ON i.ear_tag_no = inv.ear_tag_no
JOIN yak_trade_order o
ON o.id = i.order_id
LEFT JOIN yak_sn_customer bc
ON bc.id = o.buyer_id
AND bc.del_flag = '0'
LEFT JOIN yak_sn_customer sc
ON sc.id = o.seller_id
AND sc.del_flag = '0'
LEFT JOIN yak_trade_entry_record be
ON be.party_id = o.buyer_id
AND be.entry_type = 'BUYER'
LEFT JOIN yak_trade_weighing_record w
ON w.id = i.weighing_record_id
LEFT JOIN LATERAL (
SELECT o.url
FROM sys_oss o
WHERE w.photo_id IS NOT NULL
AND TRIM(w.photo_id) <> ''
AND (
o.oss_id::text = w.photo_id
OR o.ext1 = w.photo_id
OR o.file_name ILIKE '%' || w.photo_id || '%'
OR o.original_name ILIKE '%' || w.photo_id || '%'
OR o.url ILIKE '%' || w.photo_id || '%'
)
ORDER BY
CASE
WHEN o.oss_id::text = w.photo_id THEN 0
WHEN o.ext1 = w.photo_id THEN 1
ELSE 2
END
LIMIT 1
) wo ON TRUE
WHERE e.id = #{id}
AND o.status <> 'CANCELLED'
GROUP BY o.id, bc.customer_name, be.name, sc.customer_name, e.name, o.buyer_id, o.seller_id, o.created_at
ORDER BY o.created_at DESC
"""
var resolveOssUrl = (ossUrl, rawId) => {
var url = pickUrl(ossUrl)
if (url) {
return url
}
if (!rawId) {
return ''
}
var idText = (rawId + '').trim().replace(/'/g, "''")
if (!idText) {
return ''
}
var lookupSql = """
SELECT url FROM sys_oss
WHERE oss_id::text = '""" + idText + """'
OR ext1 = '""" + idText + """'
OR file_name ILIKE '%""" + idText + """%'
OR original_name ILIKE '%""" + idText + """%'
OR url ILIKE '%""" + idText + """%'
ORDER BY
CASE
WHEN oss_id::text = '""" + idText + """' THEN 0
WHEN ext1 = '""" + idText + """' THEN 1
ELSE 2
END
LIMIT 1
"""
var rows = db.select(lookupSql)
if (rows && rows.length > 0) {
return pickUrl(rows[0].url)
}
return pickUrl(idText)
}
var baseRows = db.select(baseSql)
if (!baseRows || baseRows.length == 0) {
exit 404, '未找到供应记录'
}
var base = baseRows[0]
var yakCount = base.yakCount ? base.yakCount : (base.yak_count ? base.yak_count : 0)
var soldCount = base.soldCount ? base.soldCount : (base.sold_count ? base.sold_count : 0)
var orderCount = base.orderCount ? base.orderCount : (base.order_count ? base.order_count : 0)
var progress = 0
if (yakCount > 0) {
progress = Math.round(soldCount * 100 / yakCount)
}
var entryPhotoId = base.entryPhotoId ? base.entryPhotoId : (base.entry_photo_id ? base.entry_photo_id : '')
var certFileId = base.certificateImageFileId ? base.certificateImageFileId : (base.certificate_image_file_id ? base.certificate_image_file_id : '')
var entryPhoto = resolveOssUrl(base.entryOssUrl ? base.entryOssUrl : base.entry_oss_url, entryPhotoId)
if (!entryPhoto) {
entryPhoto = pickUrl(base.carEntryImage ? base.carEntryImage : base.car_entry_image)
}
var quarantineCert = resolveOssUrl(base.certOssUrl ? base.certOssUrl : base.cert_oss_url, certFileId)
var orderRows = db.select(orderSql)
var orders = []
for (order in orderRows) {
var weightPhotoId = order.weightPhotoId ? order.weightPhotoId : (order.weight_photo_id ? order.weight_photo_id : '')
var weightPhoto = resolveOssUrl(order.weightPhotoUrl ? order.weightPhotoUrl : order.weight_photo_url, weightPhotoId)
orders.push({
id: order.id,
buyer: order.buyer ? order.buyer : '',
seller: order.seller ? order.seller : '',
tradeTime: order.tradeTime ? order.tradeTime : (order.trade_time ? order.trade_time : ''),
quantity: order.quantity ? order.quantity : 0,
weight: order.weight ? order.weight : 0,
weightPhoto: weightPhoto,
weightPhotoId: weightPhotoId
})
}
return {
id: base.id,
name: base.name ? base.name : '',
licensePlate: base.vehicleNo ? base.vehicleNo : (base.vehicle_no ? base.vehicle_no : ''),
yakCount: yakCount,
contact: base.phone ? base.phone : '',
origin: base.origin ? base.origin : '',
quarantineNo: base.quarantineNo ? base.quarantineNo : (base.quarantine_no ? base.quarantine_no : ''),
entryTime: base.entryTime ? base.entryTime : (base.entry_time ? base.entry_time : ''),
progress: progress,
tradedCount: soldCount,
pendingCount: yakCount - soldCount,
orderCount: orderCount,
entryPhoto: entryPhoto,
entryPhotoId: entryPhotoId,
quarantineCert: quarantineCert,
quarantineCertId: certFileId,
orders: orders
}

@ -0,0 +1,73 @@
{
"properties" : { },
"id" : "7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2",
"script" : null,
"groupId" : "f8e3d2c1b0a94e5f8a7b6c5d4e3f2a1",
"name" : "牦牛销售类型统计",
"createTime" : 1780880400000,
"updateTime" : null,
"lock" : null,
"createBy" : "admin",
"updateBy" : "admin",
"path" : "/yak-sales-type-stats",
"method" : "GET",
"parameters" : [ ],
"options" : [ ],
"requestBody" : "",
"headers" : [ ],
"paths" : [ ],
"responseBody" : null,
"description" : "牦牛销售类型统计:按订单 purpose 归类为屠宰/养殖/其他,统计已完成订单牦牛头数与占比(只读)。",
"requestBodyDefinition" : null,
"responseBodyDefinition" : null
}
================================
// 只读查询,不修改任何数据
// 数据源 yak_sn_order.purpose,按用途关键词归类
var sql = """
SELECT category AS name, COALESCE(SUM(quantity), 0) AS value
FROM (
SELECT
o.quantity,
CASE
WHEN TRIM(COALESCE(o.purpose, '')) ~* '(屠宰|餐饮|肉食|宰杀)' THEN '屠宰用途'
WHEN TRIM(COALESCE(o.purpose, '')) ~* '(养殖|繁育|种|批发|转售)' THEN '养殖用途'
ELSE '其他用途'
END AS category
FROM yak_sn_order o
WHERE o.del_flag = '0'
AND o.status = 'COMPLETED'
) t
GROUP BY category
"""
var rows = db.select(sql)
var categories = ['屠宰用途', '养殖用途', '其他用途']
var valueMap = {}
for (row in rows) {
valueMap[row.name] = row.value ? row.value : 0
}
var total = 0
for (cat in categories) {
var count = valueMap[cat] ? valueMap[cat] : 0
total = total + count
}
var result = []
for (cat in categories) {
var count = valueMap[cat] ? valueMap[cat] : 0
var percent = 0
if (total > 0) {
percent = (count * 100.0) / total
}
result.push({
name: cat,
value: count,
percent: percent
})
}
return result

@ -0,0 +1,74 @@
{
"properties" : { },
"id" : "a3c4d5e6f7a8490b1c2d3e4f5a67890",
"script" : null,
"groupId" : "f8e3d2c1b0a94e5f8a7b6c5d4e3f2a1",
"name" : "系统配置",
"createTime" : 1780876900000,
"updateTime" : 1780882000000,
"lock" : null,
"createBy" : "admin",
"updateBy" : "admin",
"path" : "/system-config",
"method" : "GET",
"parameters" : [ ],
"options" : [ ],
"requestBody" : "",
"headers" : [ ],
"paths" : [ ],
"responseBody" : null,
"description" : "大屏系统配置:标题、标题背景、地图枢纽节点(红原)及坐标映射等",
"requestBodyDefinition" : null,
"responseBodyDefinition" : null
}
================================
// 默认配置(可在 magic-api 编辑器中直接修改)
// mapHub.name 需与「地图迁徙数据」接口中的 hubName 保持一致
var config = {
title: '/images/标题.png',
titleBackground: '/images/标题背景.png',
mapHub: {
name: '红原县',
coordinates: [102.568685, 32.826358],
description: '四川省阿坝州红原县,中国重要的牦牛养殖基地',
localOriginKeyword: '红原'
},
mapFlowLevels: {
high: { threshold: 80, color: '#E6A23C', description: '主要流向' },
medium: { threshold: 40, color: '#409EFF', description: '重要流向' },
low: { threshold: 0, color: '#67C23A', description: '一般流向' }
},
mapGeoCoordMap: {
'红原县': [102.568685, 32.826358],
'邛溪镇': [102.515, 32.7855],
'龙日镇': [102.484, 32.3605],
'麦洼乡': [102.918, 32.9156],
'阿木乡': [102.789, 32.9154],
'刷经寺镇': [102.661, 31.9177],
'瓦切镇': [102.669, 33.2578],
'查尔玛乡': [101.921, 32.5029],
'江茸乡': [102.412, 32.3022],
'安曲镇': [102.222, 32.6709],
'色地镇': [102.999, 32.865],
'成都': [103.958004, 30.772708],
'重庆': [106.54, 29.59],
'北京': [116.46, 39.92],
'上海': [121.48, 31.22],
'天津': [117.20, 39.13],
'拉萨': [91.420246, 29.878931],
'西宁': [101.74113, 36.641678],
'兰州': [103.249708, 35.956623],
'西安': [108.95, 34.27],
'昆明': [102.72, 25.05],
'贵阳': [106.71, 26.57],
'康定': [101.956, 30.057],
'香格里拉': [99.708, 27.825],
'合作': [102.911, 34.986],
'甘南藏族自治州': [103.3049, 34.644479],
'玉树': [97.008, 33.004],
'甘孜藏族自治州': [101.226006, 30.470387]
}
}
return config

@ -0,0 +1,157 @@
{
"properties" : { },
"id" : "4d5e6f7a8490b1c2d3e4f5a678902",
"script" : null,
"groupId" : "f8e3d2c1b0a94e5f8a7b6c5d4e3f2a1",
"name" : "综合销售统计",
"createTime" : 1780877100000,
"updateTime" : null,
"lock" : null,
"createBy" : "admin",
"updateBy" : "admin",
"path" : "/comprehensive-sales-stats",
"method" : "GET",
"parameters" : [ {
"name" : "type",
"value" : null,
"description" : "统计类型:monthlySales(本月销售)/overallSalesDistribution(总体销售)/purchaseRegionDistribution(采购地区),不传则返回全部",
"required" : false,
"dataType" : "String",
"type" : null,
"defaultValue" : null,
"validateType" : null,
"error" : null,
"expression" : null,
"children" : null
} ],
"options" : [ ],
"requestBody" : "",
"headers" : [ ],
"paths" : [ ],
"responseBody" : null,
"description" : "综合销售统计:本月销售/总体销售/采购地区三维饼图数据,数据源 yak_sn_order 已完成订单(只读)",
"requestBodyDefinition" : null,
"responseBodyDefinition" : null
}
================================
// 数据源 yak_sn_order + yak_sn_customer
// value = 牦牛交易头数 quantity
var mapRows = (rows) => {
var total = 0
var result = []
for (row in rows) {
total = total + (row.value ? row.value : 0)
}
for (row in rows) {
var value = row.value ? row.value : 0
var percent = 0
if (total > 0) {
percent = (value * 100) / total
}
result.push({
name: row.name,
value: value,
percent: percent
})
}
return result
}
var monthlySql = """
SELECT town AS name, COALESCE(SUM(quantity), 0) AS value
FROM (
SELECT
o.quantity,
COALESCE(
NULLIF((regexp_match(o.origin_place, '([^省市区县]+(?:镇|乡|街道))'))[1], ''),
NULLIF(TRIM(o.origin_place), ''),
'未知地区'
) AS town
FROM yak_sn_order o
CROSS JOIN (
SELECT CASE
WHEN EXISTS (
SELECT 1 FROM yak_sn_order x
WHERE x.del_flag = '0'
AND x.status = 'COMPLETED'
AND x.transaction_time >= date_trunc('month', CURRENT_DATE)
AND x.transaction_time < date_trunc('month', CURRENT_DATE) + INTERVAL '1 month'
) THEN date_trunc('month', CURRENT_DATE)
ELSE date_trunc('month', (
SELECT MAX(transaction_time) FROM yak_sn_order
WHERE del_flag = '0' AND status = 'COMPLETED'
))
END AS month_start
) m
WHERE o.del_flag = '0'
AND o.status = 'COMPLETED'
AND o.transaction_time >= m.month_start
AND o.transaction_time < m.month_start + INTERVAL '1 month'
) t
GROUP BY town
ORDER BY value DESC
LIMIT 5
"""
var overallSql = """
SELECT town AS name, COALESCE(SUM(quantity), 0) AS value
FROM (
SELECT
o.quantity,
COALESCE(
NULLIF((regexp_match(o.origin_place, '([^省市区县]+(?:镇|乡|街道))'))[1], ''),
NULLIF(TRIM(o.origin_place), ''),
'未知地区'
) AS town
FROM yak_sn_order o
WHERE o.del_flag = '0'
AND o.status = 'COMPLETED'
) t
GROUP BY town
ORDER BY value DESC
LIMIT 5
"""
var purchaseSql = """
SELECT region AS name, COALESCE(SUM(quantity), 0) AS value
FROM (
SELECT
o.quantity,
COALESCE(
NULLIF(TRIM(b.region_name), ''),
NULLIF((regexp_match(o.destination_place, '([^省市区县]+(?:市|州|盟|县|区))'))[1], ''),
NULLIF(TRIM(o.destination_place), ''),
'未知地区'
) AS region
FROM yak_sn_order o
LEFT JOIN yak_sn_customer b
ON b.id = o.buyer_id
AND b.del_flag = '0'
WHERE o.del_flag = '0'
AND o.status = 'COMPLETED'
) t
GROUP BY region
ORDER BY value DESC
LIMIT 5
"""
var monthlyRows = db.select(monthlySql)
var overallRows = db.select(overallSql)
var purchaseRows = db.select(purchaseSql)
var result = {
monthlySales: mapRows(monthlyRows),
overallSalesDistribution: mapRows(overallRows),
purchaseRegionDistribution: mapRows(purchaseRows)
}
var t = type
if (t && result[t]) {
return result[t]
}
return result

@ -0,0 +1,55 @@
{
"properties" : { },
"id" : "8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3",
"script" : null,
"groupId" : "f8e3d2c1b0a94e5f8a7b6c5d4e3f2a1",
"name" : "采购商户来源分析",
"createTime" : 1780881000000,
"updateTime" : null,
"lock" : null,
"createBy" : "admin",
"updateBy" : "admin",
"path" : "/buyer-source-analysis",
"method" : "GET",
"parameters" : [ ],
"options" : [ ],
"requestBody" : "",
"headers" : [ ],
"paths" : [ ],
"responseBody" : null,
"description" : "采购商户来源分析:按地区统计采购商户(BUYER)数量 Top9,数据源 yak_sn_customer(只读)。",
"requestBodyDefinition" : null,
"responseBodyDefinition" : null
}
================================
// 只读查询,不修改任何数据
var sql = """
SELECT
region_name AS name,
region_name AS full_name,
COUNT(*) AS value,
region_name || '采购商户' AS description
FROM yak_sn_customer c
WHERE c.del_flag = '0'
AND c.customer_type = 'BUYER'
AND c.region_name IS NOT NULL
AND TRIM(c.region_name) <> ''
GROUP BY region_name
ORDER BY value DESC, region_name
LIMIT 9
"""
var rows = db.select(sql)
var result = []
for (row in rows) {
result.push({
name: row.name,
value: row.value ? row.value : 0,
fullName: row.fullName ? row.fullName : (row.full_name ? row.full_name : ''),
description: row.description ? row.description : '采购商户'
})
}
return result

@ -0,0 +1,14 @@
{
"properties" : { },
"id" : "d9addaf6c9904669b19434d023e8e55e",
"name" : "测试分组",
"type" : "api",
"parentId" : "0",
"path" : "/test",
"createTime" : 1780801394664,
"updateTime" : null,
"createBy" : "admin",
"updateBy" : null,
"paths" : [ ],
"options" : [ ]
}

@ -0,0 +1,25 @@
{
"properties" : { },
"id" : "df98822a60294a7ea789d1f81f1e0e85",
"script" : null,
"groupId" : "d9addaf6c9904669b19434d023e8e55e",
"name" : "测试接口",
"createTime" : 1780801413284,
"updateTime" : null,
"lock" : null,
"createBy" : "admin",
"updateBy" : null,
"path" : "/api1",
"method" : "GET",
"parameters" : [ ],
"options" : [ ],
"requestBody" : null,
"headers" : [ ],
"paths" : [ ],
"responseBody" : null,
"description" : null,
"requestBodyDefinition" : null,
"responseBodyDefinition" : null
}
================================
return 'Hello magic-api'

@ -0,0 +1,234 @@
大屏 Magic-API 接口速查
========================================
分组:/dashboard
前端代理:/api/dashboard/*
源文件:livestock-trading-backend/data/magic-api/api/大屏数据/*.ms
说明:均为只读;复杂逻辑见各 .ms 文件。修改 .ms 后需重启后端。
--------------------------------------------------------------------------------
1. 系统配置
--------------------------------------------------------------------------------
GET /dashboard/system-config
文件:系统配置.ms | 前端:Dashboard.vue、ChinaMap.vue、systemConfig.js
业务:大屏静态配置(标题图、地图枢纽红原、城市坐标、流向等级配色等)。
数据表:无(脚本内硬编码,可在 magic-api 或 .ms 中改)
--------------------------------------------------------------------------------
2. 实时交易统计
--------------------------------------------------------------------------------
GET /dashboard/real-time-stats
文件:实时交易统计.ms | 前端:RealTimeStats.vue
业务:按时间维度统计交易所已完成订单的交易量、订单数、买卖商户数。
参数:dimension(可选)day / week / month / year,不传返回全部
表:yak_sn_order
字段:quantity, seller_id, buyer_id, transaction_time, status, del_flag
条件:del_flag='0',status='COMPLETED'
--------------------------------------------------------------------------------
3. 交易所牦牛成交数据
--------------------------------------------------------------------------------
GET /dashboard/yak-trading-data
文件:交易所牦牛成交数据.ms | 前端:YakTradingData.vue
业务:近 6 个日/周/月时间桶的牦牛头数与订单数趋势。
参数:period(可选)day / week / month,不传返回全部
表:yak_sn_order
字段:id, quantity, transaction_time, status, del_flag
条件:del_flag='0',status='COMPLETED'
--------------------------------------------------------------------------------
4. 综合销售统计
--------------------------------------------------------------------------------
GET /dashboard/comprehensive-sales-stats
文件:综合销售统计.ms | 前端:ComprehensiveSalesStats.vue
业务:三个饼图——本月销售、总体销售(按产地乡镇 Top5)、采购地区(Top5)。
参数:type(可选)monthlySales / overallSalesDistribution / purchaseRegionDistribution
表:yak_sn_order;采购地区 LEFT JOIN yak_sn_customer
字段:
yak_sn_order — quantity, origin_place, destination_place, buyer_id, transaction_time, status, del_flag
yak_sn_customer — id, region_name, del_flag
条件:del_flag='0',status='COMPLETED'
--------------------------------------------------------------------------------
5. 活牛/鲜肉价格趋势
--------------------------------------------------------------------------------
GET /dashboard/price-trend
文件:活牛鲜肉价格趋势.ms | 前端:YakPriceTrend.vue
业务:近 8 个月活牛、鲜肉市场采集均价(不做重量折算)。
表:yak_trade_market_price
字段:price_date, price_type(LIVE_YAK / FRESH_MEAT), price, unit
--------------------------------------------------------------------------------
6. 交易中心实时服务信息
--------------------------------------------------------------------------------
GET /dashboard/exchange-service-info
文件:交易中心实时服务信息.ms | 前端:ExchangeMonitor.vue
业务:交易中心实时概览——供应/待售/已售头数、剩余车位、今日进场车辆、供应商数。
表及字段:
yak_trade_ear_tag_inventory — status(计总供应/待售/已售)
yak_car_parking_zone — total_capacity, current_count, status
yak_car_record — plate_no, entry_time, del_flag
yak_trade_entry_record — vehicle_no, entered_at
yak_sn_customer — customer_type='SELLER', del_flag
--------------------------------------------------------------------------------
7. 牦牛销售类型统计
--------------------------------------------------------------------------------
GET /dashboard/yak-sales-type-stats
文件:牦牛销售类型统计.ms | 前端:YakSalesTypeStats.vue
业务:按订单用途归类为屠宰/养殖/其他,统计头数与占比。
表:yak_sn_order
字段:quantity, purpose, status, del_flag
条件:del_flag='0',status='COMPLETED'
--------------------------------------------------------------------------------
8. 采购商户来源分析
--------------------------------------------------------------------------------
GET /dashboard/buyer-source-analysis
文件:采购商户来源分析.ms | 前端:PurchaserAnalysis.vue
业务:按地区统计采购商户(BUYER)数量 Top9。
表:yak_sn_customer
字段:region_name, customer_type, del_flag
条件:customer_type='BUYER',del_flag='0',region_name 非空
--------------------------------------------------------------------------------
9. 牦牛供应实时信息
--------------------------------------------------------------------------------
GET /dashboard/yak-supply-realtime-info
文件:牦牛供应实时信息.ms | 前端:SupplyDemandData.vue
业务:卖方进场登记列表(分页)及汇总(今日进场、总头数、交易中批次)。
参数:pageNo(默认 1)、pageSize(默认 5;勿用 page)
表及字段:
yak_trade_entry_record — id, name, vehicle_no, phone, party_id, quarantine_certificate_no, entered_at, entry_type
yak_trade_ear_tag_inventory — seller_entry_record_id, status, ear_tag_no
yak_trade_entry_record_ear_tag — entry_record_id
yak_sn_customer — id, region_name, address
条件:entry_type='SELLER'
--------------------------------------------------------------------------------
10. 牦牛供应详情
--------------------------------------------------------------------------------
GET /dashboard/yak-supply-detail
文件:牦牛供应详情.ms | 前端:SupplyDemandData.vue
业务:单条卖方进场详情(基本信息、耳标进度、关联成交订单、进场/检疫/称重图片 URL)。
参数:id(必填,yak_trade_entry_record.id)
表及字段:
yak_trade_entry_record — id, name, vehicle_no, phone, party_id, quarantine_certificate_no, entry_photo_id, entered_at, entry_type
yak_trade_ear_tag_inventory — seller_entry_record_id, ear_tag_no, status, locked_order_id
yak_trade_entry_record_ear_tag — entry_record_id, certificate_image_file_id
yak_trade_order / yak_trade_order_item — 订单与耳标明细
yak_trade_weighing_record — weight, photo_id
yak_trade_quarantine_certificate — certificate_no, image_file_id
yak_sn_customer — region_name, address(买卖方名称)
yak_car_record — 关联车辆
sys_oss — url(图片解析)
--------------------------------------------------------------------------------
11. 市场环境监控
--------------------------------------------------------------------------------
GET /dashboard/market-environment
文件:市场环境监控.ms | 前端:MarketEnvironmentMonitor.vue
业务:环境温湿度 + 气象数据各取最新一条。
表及字段:
iot_device_env_data — temperature, humidity, collect_time
iot_device_weather_data — air_pressure, pm25, pm10, uv_index, rainfall, wind_speed, wind_direction, collect_time
--------------------------------------------------------------------------------
12. 市场实时监控
--------------------------------------------------------------------------------
GET /dashboard/market-realtime-monitor
文件:市场实时监控.ms | 前端:MarketRealtimeMonitor.vue
业务:视频监控设备列表及播放/预览地址。
表及字段:
iot_device_video — id, name, number, location, address, status, play_url, hd_play_url, preview_img_url, channel_number, index, del_flag, is_show
iot_device_video_data — device_id, stream_url, snapshot_url, online_status, stream_status, fault_code, collect_time
条件:del_flag='0',is_show=true
--------------------------------------------------------------------------------
13. 市场实时监控播放配置
--------------------------------------------------------------------------------
GET /dashboard/market-realtime-player-config
文件:市场实时监控播放配置.ms | 前端:MarketRealtimeMonitor.vue(liveStreamPlayer.js)
业务:直播流协议识别规则与 hls.js / flv.js 播放器参数(静态配置)。
数据表:无
--------------------------------------------------------------------------------
14. 地图迁徙数据
--------------------------------------------------------------------------------
GET /dashboard/map-trading-network
文件:地图迁徙数据.ms | 前端:ChinaMap.vue
业务:中央地图三种模式流向——销售网络(红原→全国)、源地供应(全国→红原)、红原出栏(乡镇→红原)。
表:yak_sn_order;outflow/inflow LEFT JOIN yak_sn_customer
字段:
yak_sn_order — quantity, origin_place, destination_place, buyer_id, seller_id, status, del_flag
yak_sn_customer — id, region_name, del_flag
条件:del_flag='0',status='COMPLETED';local 模式另要求 origin_place 含「红原」
关联:系统配置 mapHub.name 与脚本 hubName 需一致(默认「红原县」)
================================================================================
表 → 接口索引
================================================================================
yak_sn_order 2, 3, 4, 7, 14
yak_sn_customer 4, 6, 8, 9, 10, 14
yak_trade_market_price 5
yak_trade_ear_tag_inventory 6, 9, 10
yak_trade_entry_record 6, 9, 10
yak_trade_entry_record_ear_tag 9, 10
yak_trade_order / _item 10
yak_trade_weighing_record 10
yak_trade_quarantine_certificate 10
yak_car_parking_zone 6
yak_car_record 6, 10
sys_oss 10
iot_device_env_data 11
iot_device_weather_data 11
iot_device_video 12
iot_device_video_data 12
(无表) 1, 13

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.5</version>
<relativePath/>
</parent>
<groupId>com.livestock</groupId>
<artifactId>livestock-trading-backend</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>livestock-trading-backend</name>
<description>Livestock trading backend based on magic-api</description>
<properties>
<java.version>17</java.version>
<magic-api.version>2.2.2</magic-api.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.ssssssss</groupId>
<artifactId>magic-api-spring-boot-starter</artifactId>
<version>${magic-api.version}</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,12 @@
package com.livestock.trading;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class LivestockTradingApplication {
public static void main(String[] args) {
SpringApplication.run(LivestockTradingApplication.class, args);
}
}

@ -0,0 +1,33 @@
server:
port: 8080
spring:
application:
name: livestock-trading-backend
datasource:
driver-class-name: org.postgresql.Driver
url: jdbc:postgresql://123.57.211.165:15432/ry-vue
username: postgres
password: admin123
magic-api:
web: /magic/web
resource:
location: ./data/magic-api
show-sql: true
sql-column-case: camel
response: |-
{
code: code,
message: message,
data,
timestamp,
executeTime
}
response-code:
success: 1
invalid: 0
exception: -1
security:
username: admin
password: admin123

@ -0,0 +1,12 @@
package com.livestock.trading;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class LivestockTradingApplicationTests {
@Test
void contextLoads() {
}
}
Loading…
Cancel
Save