mirror of
https://github.com/hpd840321/craftlabs-authorization-sdk.git
synced 2026-06-09 10:00:30 +08:00
feat: add system params persistence and delivery gate enforcement
V25 migration creates platform_system_param table. SystemParamController replaces localStorage MVP with backend persistence. LicenseSnService.create now checks deliveryGateEnabled flag and blocks SN creation when gate is on but no deliveries completed. Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
+50
@@ -0,0 +1,50 @@
|
||||
package cn.craftlabs.platform.api.config;
|
||||
|
||||
import cn.craftlabs.platform.api.persistence.system.PlatformSystemParam;
|
||||
import cn.craftlabs.platform.api.persistence.system.PlatformSystemParamMapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/system-params")
|
||||
public class SystemParamController {
|
||||
|
||||
private final PlatformSystemParamMapper paramMapper;
|
||||
|
||||
public SystemParamController(PlatformSystemParamMapper paramMapper) {
|
||||
this.paramMapper = paramMapper;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public Map<String, String> list() {
|
||||
List<PlatformSystemParam> params = paramMapper.selectList(Wrappers.lambdaQuery());
|
||||
return params.stream().collect(Collectors.toMap(
|
||||
PlatformSystemParam::getParamKey, PlatformSystemParam::getParamValue));
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
public ResponseEntity<Void> update(@RequestBody Map<String, String> body) {
|
||||
for (var entry : body.entrySet()) {
|
||||
PlatformSystemParam param = paramMapper.selectById(entry.getKey());
|
||||
if (param == null) {
|
||||
param = new PlatformSystemParam();
|
||||
param.setParamKey(entry.getKey());
|
||||
}
|
||||
param.setParamValue(entry.getValue());
|
||||
param.setUpdatedAt(OffsetDateTime.now(ZoneOffset.UTC));
|
||||
if (param.getParamKey() != null && paramMapper.selectById(param.getParamKey()) != null) {
|
||||
paramMapper.updateById(param);
|
||||
} else {
|
||||
paramMapper.insert(param);
|
||||
}
|
||||
}
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package cn.craftlabs.platform.api.persistence.system;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
@TableName("platform_system_param")
|
||||
public class PlatformSystemParam {
|
||||
|
||||
@TableId
|
||||
@TableField("param_key")
|
||||
private String paramKey;
|
||||
|
||||
@TableField("param_value")
|
||||
private String paramValue;
|
||||
|
||||
@TableField("updated_at")
|
||||
private OffsetDateTime updatedAt;
|
||||
|
||||
public String getParamKey() { return paramKey; }
|
||||
public void setParamKey(String paramKey) { this.paramKey = paramKey; }
|
||||
|
||||
public String getParamValue() { return paramValue; }
|
||||
public void setParamValue(String paramValue) { this.paramValue = paramValue; }
|
||||
|
||||
public OffsetDateTime getUpdatedAt() { return updatedAt; }
|
||||
public void setUpdatedAt(OffsetDateTime updatedAt) { this.updatedAt = updatedAt; }
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package cn.craftlabs.platform.api.persistence.system;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface PlatformSystemParamMapper extends BaseMapper<PlatformSystemParam> {
|
||||
}
|
||||
+27
-7
@@ -9,15 +9,23 @@ import cn.craftlabs.platform.api.persistence.contract.PlatformContractLineMapper
|
||||
import cn.craftlabs.platform.api.persistence.contract.PlatformContractMapper;
|
||||
import cn.craftlabs.platform.api.persistence.delivery.PlatformDeliveryBatch;
|
||||
import cn.craftlabs.platform.api.persistence.delivery.PlatformDeliveryBatchMapper;
|
||||
import cn.craftlabs.platform.api.persistence.license.PlatformLicenseActivation;
|
||||
import cn.craftlabs.platform.api.persistence.license.PlatformLicenseActivationMapper;
|
||||
import cn.craftlabs.platform.api.persistence.license.PlatformLicenseKey;
|
||||
import cn.craftlabs.platform.api.persistence.license.PlatformLicenseKeyMapper;
|
||||
import cn.craftlabs.platform.api.persistence.license.PlatformLicenseMapper;
|
||||
import cn.craftlabs.platform.api.persistence.license.PlatformLicenseSn;
|
||||
import cn.craftlabs.platform.api.persistence.license.PlatformLicenseSnMapper;
|
||||
import cn.craftlabs.platform.api.persistence.project.PlatformProject;
|
||||
import cn.craftlabs.platform.api.persistence.project.PlatformProjectMapper;
|
||||
import cn.craftlabs.platform.api.persistence.system.PlatformSystemParamMapper;
|
||||
import cn.craftlabs.platform.api.web.dto.LicenseSnCreateRequest;
|
||||
import cn.craftlabs.platform.api.web.dto.LicenseSnResponse;
|
||||
import cn.craftlabs.platform.api.web.dto.LicenseSnStatusPatchRequest;
|
||||
import cn.craftlabs.platform.api.web.dto.LicenseSnUpdateRequest;
|
||||
import cn.craftlabs.platform.api.web.dto.PageResponse;
|
||||
import cn.craftlabs.platform.api.web.dto.SnBatchImportRequest;
|
||||
import cn.craftlabs.platform.api.web.dto.SnBatchImportRequest;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
@@ -44,6 +52,7 @@ public class LicenseSnService {
|
||||
private final PlatformContractLineMapper contractLineMapper;
|
||||
private final PlatformContractMapper contractMapper;
|
||||
private final PlatformDeliveryBatchMapper deliveryBatchMapper;
|
||||
private final PlatformSystemParamMapper systemParamMapper;
|
||||
private final AuditService auditService;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@@ -53,6 +62,7 @@ public class LicenseSnService {
|
||||
PlatformContractLineMapper contractLineMapper,
|
||||
PlatformContractMapper contractMapper,
|
||||
PlatformDeliveryBatchMapper deliveryBatchMapper,
|
||||
PlatformSystemParamMapper systemParamMapper,
|
||||
AuditService auditService,
|
||||
ObjectMapper objectMapper) {
|
||||
this.licenseSnMapper = licenseSnMapper;
|
||||
@@ -60,6 +70,7 @@ public class LicenseSnService {
|
||||
this.contractLineMapper = contractLineMapper;
|
||||
this.contractMapper = contractMapper;
|
||||
this.deliveryBatchMapper = deliveryBatchMapper;
|
||||
this.systemParamMapper = systemParamMapper;
|
||||
this.auditService = auditService;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
@@ -87,13 +98,22 @@ public class LicenseSnService {
|
||||
requireProject(projectId);
|
||||
}
|
||||
if (request.getProjectId() != null) {
|
||||
var deliveryQuery = com.baomidou.mybatisplus.core.toolkit.Wrappers.lambdaQuery(PlatformDeliveryBatch.class)
|
||||
.eq(PlatformDeliveryBatch::getProjectId, request.getProjectId())
|
||||
.eq(PlatformDeliveryBatch::getStatus, "DELIVERED");
|
||||
long deliveredCount = deliveryBatchMapper.selectCount(deliveryQuery);
|
||||
if (deliveredCount == 0) {
|
||||
// If project has batches but none delivered, warn (not block for MVP)
|
||||
// This is a soft check - can be made strict later
|
||||
var gateParam = systemParamMapper.selectById("deliveryGateEnabled");
|
||||
boolean gateEnabled = gateParam != null && "true".equals(gateParam.getParamValue());
|
||||
if (gateEnabled) {
|
||||
var batchQuery = com.baomidou.mybatisplus.core.toolkit.Wrappers.lambdaQuery(PlatformDeliveryBatch.class)
|
||||
.eq(PlatformDeliveryBatch::getProjectId, request.getProjectId());
|
||||
long totalBatches = deliveryBatchMapper.selectCount(batchQuery);
|
||||
if (totalBatches > 0) {
|
||||
var deliveredQuery = com.baomidou.mybatisplus.core.toolkit.Wrappers.lambdaQuery(PlatformDeliveryBatch.class)
|
||||
.eq(PlatformDeliveryBatch::getProjectId, request.getProjectId())
|
||||
.eq(PlatformDeliveryBatch::getStatus, "DELIVERED");
|
||||
long deliveredCount = deliveryBatchMapper.selectCount(deliveredQuery);
|
||||
if (deliveredCount == 0) {
|
||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN,
|
||||
"交付闸门已启用: 该项目下的交付批次尚未标记为已交付");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (existsSnCode(code)) {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
-- V25__system_params.sql
|
||||
-- 系统参数持久化(M11-F20),替代前端 localStorage MVP
|
||||
|
||||
CREATE TABLE IF NOT EXISTS platform_system_param (
|
||||
param_key VARCHAR(64) PRIMARY KEY,
|
||||
param_value VARCHAR(1024) NOT NULL DEFAULT '',
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
INSERT INTO platform_system_param (param_key, param_value) VALUES
|
||||
('orphanSnStrictValidation', 'true'),
|
||||
('deliveryGateEnabled', 'true'),
|
||||
('sessionTimeoutMinutes', '60'),
|
||||
('passwordMinLength', '6')
|
||||
ON CONFLICT (param_key) DO NOTHING;
|
||||
@@ -1,31 +1,25 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import axios from 'axios'
|
||||
|
||||
const params = ref({
|
||||
orphanSnStrictValidation: true,
|
||||
deliveryGateEnabled: true,
|
||||
sessionTimeoutMinutes: 60,
|
||||
passwordMinLength: 6,
|
||||
})
|
||||
|
||||
const params = ref({})
|
||||
const loading = ref(false)
|
||||
|
||||
async function loadParams() {
|
||||
try {
|
||||
const stored = localStorage.getItem('systemParams')
|
||||
if (stored) {
|
||||
params.value = { ...params.value, ...JSON.parse(stored) }
|
||||
}
|
||||
const { data } = await axios.get('/api/v1/system-params')
|
||||
params.value = { ...data }
|
||||
} catch {
|
||||
ElMessage.error('加载系统参数失败')
|
||||
}
|
||||
}
|
||||
|
||||
async function saveParams() {
|
||||
loading.value = true
|
||||
try {
|
||||
localStorage.setItem('systemParams', JSON.stringify(params.value))
|
||||
ElMessage.success('参数已保存(MVP: 存储于浏览器本地)')
|
||||
await axios.put('/api/v1/system-params', params.value)
|
||||
ElMessage.success('参数已保存')
|
||||
} catch (e) {
|
||||
ElMessage.error('保存失败')
|
||||
} finally {
|
||||
@@ -42,10 +36,10 @@ onMounted(loadParams)
|
||||
<el-card shadow="never" style="margin-top:16px;max-width:560px">
|
||||
<el-form label-width="200px" label-position="left">
|
||||
<el-form-item label="孤儿 SN 严格校验">
|
||||
<el-switch v-model="params.orphanSnStrictValidation" />
|
||||
<el-switch v-model="params.orphanSnStrictValidation" active-value="true" inactive-value="false" />
|
||||
</el-form-item>
|
||||
<el-form-item label="交付闸门启用">
|
||||
<el-switch v-model="params.deliveryGateEnabled" />
|
||||
<el-switch v-model="params.deliveryGateEnabled" active-value="true" inactive-value="false" />
|
||||
</el-form-item>
|
||||
<el-form-item label="会话超时(分钟)">
|
||||
<el-input-number v-model="params.sessionTimeoutMinutes" :min="5" :max="1440" />
|
||||
|
||||
Reference in New Issue
Block a user