Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -4,6 +4,7 @@ import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
/**
|
||||
* 启动程序
|
||||
@@ -11,6 +12,7 @@ import org.springframework.scheduling.annotation.EnableAsync;
|
||||
* @author ruoyi
|
||||
*/
|
||||
@EnableAsync
|
||||
@EnableScheduling
|
||||
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
|
||||
public class RuoYiApplication
|
||||
{
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package com.ruoyi.web.controller.price;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.utils.MessageUtils;
|
||||
import com.ruoyi.price.task.BmsStatusRefreshTask;
|
||||
|
||||
/**
|
||||
* 状态自动刷新 - 手动触发入口
|
||||
*
|
||||
* <p>日常由 BmsStatusRefreshTask 每天 02:00 跑,特殊情况可手动触发。
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/price/status")
|
||||
public class PriceStatusController extends BaseController
|
||||
{
|
||||
@Autowired
|
||||
private BmsStatusRefreshTask refreshTask;
|
||||
|
||||
/**
|
||||
* 立即刷新 bms_product / bms_product_country 的 status 字段
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('price:product:edit')")
|
||||
@PostMapping("/refresh")
|
||||
public AjaxResult refresh()
|
||||
{
|
||||
int[] counts = refreshTask.refreshNow();
|
||||
String msg = MessageUtils.message("price.status.refresh.success",
|
||||
String.valueOf(counts[0]), String.valueOf(counts[1]));
|
||||
return success(msg);
|
||||
}
|
||||
}
|
||||
@@ -115,4 +115,7 @@ price.quote.bizDateInvalid=业务日期格式必须为yyyy-MM-dd
|
||||
price.quote.fixedPackageNoDetails=套餐固定价格没有明细,使用产品默认价格
|
||||
price.quote.noCustomerPackageOverride=没有客户套餐覆盖,使用产品默认运费/挂号费
|
||||
price.quote.quoteIdRequired=报价ID为空
|
||||
price.quote.hitCustomerFixedPackageDetail=命中客户套餐固定价格明细
|
||||
price.quote.hitCustomerFixedPackageDetail=命中客户套餐固定价格明细
|
||||
|
||||
# ---- 状态自动刷新 ----
|
||||
price.status.refresh.success=状态刷新完成:bms_product 更新 {0} 行,bms_product_country 更新 {1} 行
|
||||
@@ -154,7 +154,15 @@ public class SysJobController extends BaseController
|
||||
@PutMapping("/changeStatus")
|
||||
public AjaxResult changeStatus(@RequestBody SysJob job) throws SchedulerException
|
||||
{
|
||||
if (job == null || job.getJobId() == null)
|
||||
{
|
||||
return error("任务ID不能为空,请刷新列表后重试");
|
||||
}
|
||||
SysJob newJob = jobService.selectJobById(job.getJobId());
|
||||
if (newJob == null)
|
||||
{
|
||||
return error("任务不存在或已被删除,请刷新列表后重试");
|
||||
}
|
||||
newJob.setStatus(job.getStatus());
|
||||
return toAjax(jobService.changeStatus(newJob));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.ruoyi.price.task;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
import com.ruoyi.price.mapper.BmsProductCountryMapper;
|
||||
import com.ruoyi.price.mapper.BmsProductMapper;
|
||||
|
||||
/**
|
||||
* bms_product / bms_product_country 状态自动刷新
|
||||
*
|
||||
* <p>每天 02:00 根据 effective_date / expire_date 与当天日期,回填 status:
|
||||
* <ul>
|
||||
* <li>'2' 已失效 EXPIRED:expire_date 非空且 <= 今天</li>
|
||||
* <li>'0' 待生效 PENDING:effective_date 为空 / 未来</li>
|
||||
* <li>'1' 已生效 ACTIVE:其余(已生效且仍在有效期)</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>对应字典 bms_product_effect_status 的 value=ACTIVE/PENDING/EXPIRED。
|
||||
* 实际存储使用 0/1/2 与列 comment 保持一致;前端 dict-tag 需做 value 映射或调整 dict_value。
|
||||
*
|
||||
* <p>手动触发:调用 refreshNow()(运维/测试用,详见 StatusRefreshController)。
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Component
|
||||
public class BmsStatusRefreshTask
|
||||
{
|
||||
private static final Logger log = LoggerFactory.getLogger(BmsStatusRefreshTask.class);
|
||||
|
||||
@Autowired
|
||||
private BmsProductMapper productMapper;
|
||||
|
||||
@Autowired
|
||||
private BmsProductCountryMapper productCountryMapper;
|
||||
|
||||
/** 每天 02:00 跑(cron: sec min hour DOM month DOW) */
|
||||
@Scheduled(cron = "0 0 2 * * ?")
|
||||
public void scheduled()
|
||||
{
|
||||
refreshNow();
|
||||
}
|
||||
|
||||
/**
|
||||
* 立即刷新两表状态。返回 [product 影响行数, country 影响行数]。
|
||||
* 异常时打印 ERROR 并原样抛出,便于调用方(手动触发接口)回滚事务。
|
||||
*/
|
||||
public int[] refreshNow()
|
||||
{
|
||||
int productCount = productMapper.refreshStatusByDates();
|
||||
int countryCount = productCountryMapper.refreshStatusByDates();
|
||||
log.info("BmsStatusRefreshTask done: bms_product={}, bms_product_country={}",
|
||||
productCount, countryCount);
|
||||
return new int[] { productCount, countryCount };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
-- ============================================================
|
||||
-- bms_product / bms_product_country 自动状态回填
|
||||
-- ============================================================
|
||||
-- 背景:
|
||||
-- bms_product.status char(1) DEFAULT '0' COMMENT '0待生效 1已生效 2已失效'
|
||||
-- bms_product_country.status char(1) DEFAULT '0' COMMENT '0待生效 1已生效 2已失效'
|
||||
-- 字典 bms_product_effect_status value=ACTIVE/PENDING/EXPIRED
|
||||
-- 自动刷新:
|
||||
-- BmsStatusRefreshTask 每天 02:00 调用两表 Mapper.refreshStatusByDates()
|
||||
-- 即本文件末尾的回填 SQL(每日定时跑同样效果)
|
||||
-- 现状验证:
|
||||
-- bms_product.status 当前值仅 '0' / '1'('2' = EXPIRED 尚未触发,因今天 2026-09-09
|
||||
-- 还没到任一 expire_date);bms_product_country.status 同上
|
||||
-- ============================================================
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
-- ---------- 一次性回填(幂等:重跑结果一致) ----------
|
||||
-- bms_product:
|
||||
-- expire_date 非空且 <= 今天 → '2' 已失效
|
||||
-- effective_date 为空 / 未来 → '0' 待生效
|
||||
-- 其余(已生效且仍在有效期) → '1' 已生效
|
||||
UPDATE bms_product p
|
||||
SET p.status = (
|
||||
CASE
|
||||
WHEN p.expire_date IS NOT NULL AND p.expire_date <= CURDATE() THEN '2'
|
||||
WHEN p.effective_date IS NULL OR p.effective_date > CURDATE() THEN '0'
|
||||
ELSE '1'
|
||||
END
|
||||
);
|
||||
|
||||
-- bms_product_country:同上规则(多了 detail 级过期判断)
|
||||
UPDATE bms_product_country c
|
||||
SET c.status = (
|
||||
CASE
|
||||
WHEN c.expire_date IS NOT NULL AND c.expire_date <= CURDATE() THEN '2'
|
||||
WHEN c.effective_date IS NULL OR c.effective_date > CURDATE() THEN '0'
|
||||
ELSE '1'
|
||||
END
|
||||
);
|
||||
@@ -0,0 +1,15 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 立即刷新 bms_product / bms_product_country 的 status 字段
|
||||
*
|
||||
* 日常由后端定时任务 BmsStatusRefreshTask 每天 02:00 自动跑,
|
||||
* 此接口用于运维/测试手动触发。返回 success msg 形如:
|
||||
* 「状态刷新完成:bms_product 更新 X 行,bms_product_country 更新 Y 行」
|
||||
*/
|
||||
export function refreshStatus() {
|
||||
return request({
|
||||
url: '/price/status/refresh',
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
@@ -63,6 +63,7 @@
|
||||
|
||||
<template #col-status="{ row }">
|
||||
<el-switch
|
||||
v-if="row.jobId != null"
|
||||
v-model="row.status"
|
||||
active-value="0"
|
||||
inactive-value="1"
|
||||
@@ -308,8 +309,14 @@ function handleDelete(jobIds) {
|
||||
|
||||
// 任务状态修改
|
||||
function handleStatusChange(row) {
|
||||
// 行数据不完整(jobId 缺失,常见于加载瞬间/新增未刷新),把开关拨回并拦截,避免弹 "undefined" 确认框
|
||||
if (!row || row.jobId == null) {
|
||||
if (row) row.status = row.status === "0" ? "1" : "0"
|
||||
proxy.$modal.msgWarning("任务数据无效,请刷新列表后重试")
|
||||
return
|
||||
}
|
||||
let text = row.status === "0" ? "启用" : "停用"
|
||||
proxy.$modal.confirm('确认要"' + text + '""' + row.jobName + '"任务吗?').then(function () {
|
||||
proxy.$modal.confirm('确认要"' + text + '""' + (row.jobName || row.jobId) + '"任务吗?').then(function () {
|
||||
return changeJobStatus(row.jobId, row.status)
|
||||
}).then(() => {
|
||||
proxy.$modal.msgSuccess(text + "成功")
|
||||
|
||||
@@ -38,6 +38,9 @@
|
||||
<el-col :span="1.5">
|
||||
<el-button type="info" plain icon="Upload" @click="handleImport" v-hasPermi="['price:product:import']">导入 Excel</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="Refresh" @click="handleRefreshStatus" v-hasPermi="['price:product:edit']">刷新状态</el-button>
|
||||
</el-col>
|
||||
</template>
|
||||
|
||||
<template #col-cargoAttr="{ row }">
|
||||
@@ -76,6 +79,7 @@ import ProductFormDialog from './components/ProductFormDialog.vue'
|
||||
import ProductCountryDrawer from './components/ProductCountryDrawer.vue'
|
||||
import ProductSurchargeDrawer from './components/ProductSurchargeDrawer.vue'
|
||||
import { listProduct, delProduct } from '@/api/price/product'
|
||||
import { refreshStatus } from '@/api/price/status'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
const { bms_product_effect_status } = useDict('bms_product_effect_status')
|
||||
@@ -183,6 +187,19 @@ function handleDelete(ids) {
|
||||
function handleImport() {
|
||||
importRef.value.open()
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动触发 bms_product / bms_product_country 状态刷新
|
||||
* 日常由后端定时任务每天 02:00 自动跑;此处用于运维即时刷新
|
||||
*/
|
||||
function handleRefreshStatus() {
|
||||
proxy.$modal.confirm('将立即按 effective_date / expire_date 重新计算所有产品及国家明细的状态,是否继续?').then(() => {
|
||||
return refreshStatus()
|
||||
}).then(res => {
|
||||
proxy.$modal.msgSuccess(res.msg || '刷新成功')
|
||||
tableRef.value && tableRef.value.refresh()
|
||||
}).catch(() => {})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user