yanderong
2026-10-02 f50643366a89ea4548cabddf3cbc783605a6f167
feat: 储能光伏账号配置
11个文件已添加
559 ■■■■■ 已修改文件
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/TPlatformConfigController.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/domain/PlatformEnum.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/domain/TPlatformConfig.java 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/dto/TPlatformConfigBatchSaveDTO.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/mapper/TPlatformConfigMapper.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/TPlatformConfigService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/impl/TPlatformConfigServiceImpl.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/resources/mapper/other/TPlatformConfigMapper.xml 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/resources/sql/t_platform_config.sql 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/test/java/com/ruoyi/other/controller/TPlatformConfigControllerTest.java 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/test/java/com/ruoyi/other/service/impl/TPlatformConfigServiceImplTest.java 166 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/TPlatformConfigController.java
New file
@@ -0,0 +1,50 @@
package com.ruoyi.other.controller;
import com.ruoyi.common.core.web.domain.AjaxResult;
import com.ruoyi.common.log.annotation.Log;
import com.ruoyi.common.log.enums.BusinessType;
import com.ruoyi.common.security.annotation.RequiresPermissions;
import com.ruoyi.other.domain.TPlatformConfig;
import com.ruoyi.other.dto.TPlatformConfigBatchSaveDTO;
import com.ruoyi.other.service.TPlatformConfigService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import java.util.List;
/** 光伏储能配置接口。 */
@Api(tags = "后台-光伏储能配置")
@RestController
@RequestMapping("/platform-config")
public class TPlatformConfigController {
    private final TPlatformConfigService platformConfigService;
    public TPlatformConfigController(TPlatformConfigService platformConfigService) {
        this.platformConfigService = platformConfigService;
    }
    @RequiresPermissions("/platformConfig")
    @ApiOperation("查询所有未删除的平台配置(不分页)")
    @GetMapping("/listAll")
    public AjaxResult<List<TPlatformConfig>> listAll() {
        return AjaxResult.ok(platformConfigService.listAll());
    }
    @RequiresPermissions("/platformConfig")
    @ApiOperation(value = "批量保存平台配置(无ID新增,有ID修改)",
            notes = "请求体为JSON对象,configs字段为配置数组")
    @PostMapping("/saveBatch")
    @Log(title = "批量保存光伏储能配置", businessType = BusinessType.UPDATE,
            isSaveRequestData = false, isSaveResponseData = false)
    public AjaxResult<Void> saveBatch(@Valid @RequestBody TPlatformConfigBatchSaveDTO request) {
        platformConfigService.saveBatchConfigs(request.getConfigs());
        return AjaxResult.ok(null);
    }
}
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/domain/PlatformEnum.java
New file
@@ -0,0 +1,39 @@
package com.ruoyi.other.domain;
public enum PlatformEnum {
    KSTAR(0, "科士达"),
    HUAWEI(1, "华为"),
    SUNGROW(2, "阳光"),
    GROWATT(3, "古瑞瓦特"),
    OULITE(4, "欧力特");;
    private final Integer code;
    private final String name;
    PlatformEnum(Integer code, String name) {
        this.code = code;
        this.name = name;
    }
    public Integer getCode() {
        return code;
    }
    public String getName() {
        return name;
    }
    /**
     * 根据code获取枚举
     */
    public static PlatformEnum getByCode(Integer code) {
        for (PlatformEnum item : values()) {
            if (item.code.equals(code)) {
                return item;
            }
        }
        return null;
    }
}
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/domain/TPlatformConfig.java
New file
@@ -0,0 +1,84 @@
package com.ruoyi.other.domain;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.ToString;
import java.io.Serializable;
import java.time.LocalDateTime;
/** 光伏储能平台配置。 */
@Data
@TableName("t_platform_config")
@ApiModel(value = "TPlatformConfig", description = "光伏储能配置表")
public class TPlatformConfig implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.AUTO)
    @ApiModelProperty("主键ID,新增时不传,修改时传入")
    private Long id;
    @TableField("type")
    @ApiModelProperty("类型(1:光伏,2:储能)")
    private Integer type;
    @TableField("platform_name")
    @ApiModelProperty("所属平台(利普芯、欧力特)")
    private String platformName;
    @TableField("platform_id")
    @ApiModelProperty("所属平台ID")
    private Integer platformId;
    @TableField("account")
    @ApiModelProperty("账号")
    private String account;
    @ToString.Exclude
    @TableField("password")
    @ApiModelProperty("密码")
    private String password;
    @ToString.Exclude
    @TableField("app_key")
    @ApiModelProperty("平台KEY")
    private String appKey;
    @ToString.Exclude
    @TableField("app_secret")
    @ApiModelProperty("平台密钥")
    private String appSecret;
    @TableField("app_path")
    @ApiModelProperty("平台请求地址")
    private String appPath;
    @TableField("remark")
    @ApiModelProperty("备注")
    private String remark;
    @TableField(value = "create_time", fill = FieldFill.INSERT, updateStrategy = FieldStrategy.NEVER)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @ApiModelProperty(value = "创建时间,由服务端维护", readOnly = true)
    private LocalDateTime createTime;
    @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @ApiModelProperty(value = "更新时间,由服务端维护", readOnly = true)
    private LocalDateTime updateTime;
    @TableField("del_flag")
    @TableLogic(value = "0", delval = "2")
    @ApiModelProperty(value = "删除标志(0存在,2删除),由服务端维护", readOnly = true)
    private String delFlag;
}
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/dto/TPlatformConfigBatchSaveDTO.java
New file
@@ -0,0 +1,19 @@
package com.ruoyi.other.dto;
import com.ruoyi.other.domain.TPlatformConfig;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import java.util.List;
/** 使用顶层 JSON 对象承载配置列表,兼容网关的签名参数解析。 */
@Data
@ApiModel(value = "TPlatformConfigBatchSaveDTO", description = "批量保存光伏储能配置请求")
public class TPlatformConfigBatchSaveDTO {
    @NotEmpty(message = "保存的配置列表不能为空")
    @ApiModelProperty(value = "配置列表,无ID新增,有ID修改", required = true)
    private List<TPlatformConfig> configs;
}
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/mapper/TPlatformConfigMapper.java
New file
@@ -0,0 +1,8 @@
package com.ruoyi.other.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.other.domain.TPlatformConfig;
/** 光伏储能配置 Mapper。 */
public interface TPlatformConfigMapper extends BaseMapper<TPlatformConfig> {
}
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/TPlatformConfigService.java
New file
@@ -0,0 +1,16 @@
package com.ruoyi.other.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.other.domain.TPlatformConfig;
import java.util.List;
/** 光伏储能配置服务。 */
public interface TPlatformConfigService extends IService<TPlatformConfig> {
    /** 不分页查询所有未删除配置,按主键升序排列。 */
    List<TPlatformConfig> listAll();
    /** 批量保存:无 ID 新增,有 ID 修改;任一失败整批回滚。 */
    void saveBatchConfigs(List<TPlatformConfig> configs);
}
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/impl/TPlatformConfigServiceImpl.java
New file
@@ -0,0 +1,57 @@
package com.ruoyi.other.service.impl;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.core.exception.ServiceException;
import com.ruoyi.other.domain.TPlatformConfig;
import com.ruoyi.other.mapper.TPlatformConfigMapper;
import com.ruoyi.other.service.TPlatformConfigService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/** 光伏储能配置服务实现。 */
@Service
public class TPlatformConfigServiceImpl extends ServiceImpl<TPlatformConfigMapper, TPlatformConfig>
        implements TPlatformConfigService {
    @Override
    public List<TPlatformConfig> listAll() {
        return list(Wrappers.lambdaQuery(TPlatformConfig.class).orderByAsc(TPlatformConfig::getId));
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveBatchConfigs(List<TPlatformConfig> configs) {
        if (configs == null || configs.isEmpty()) {
            throw new ServiceException("保存的配置列表不能为空");
        }
        Set<Long> ids = new HashSet<>();
        for (TPlatformConfig config : configs) {
            if (config == null) {
                throw new ServiceException("配置列表不能包含空数据");
            }
            if (config.getType() != null && config.getType() != 1 && config.getType() != 2) {
                throw new ServiceException("配置类型只能为1(光伏)或2(储能)");
            }
            if (config.getId() != null && (config.getId() <= 0 || !ids.add(config.getId()))) {
                throw new ServiceException("配置ID必须为正数且不能重复");
            }
        }
        // 传入 ID 必须对应有效配置,避免误将已删除或不存在的记录作为新增保存。
        if (!ids.isEmpty() && listByIds(ids).size() != ids.size()) {
            throw new ServiceException("待修改的配置不存在或已删除");
        }
        for (TPlatformConfig config : configs) {
            config.setDelFlag("0");
            config.setCreateTime(null);
            config.setUpdateTime(null);
        }
        if (!saveOrUpdateBatch(configs)) {
            throw new ServiceException("批量保存平台配置失败");
        }
    }
}
ruoyi-service/ruoyi-other/src/main/resources/mapper/other/TPlatformConfigMapper.xml
New file
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.other.mapper.TPlatformConfigMapper">
    <resultMap id="BaseResultMap" type="com.ruoyi.other.domain.TPlatformConfig">
        <id column="id" property="id" />
        <result column="type" property="type" />
        <result column="platform_name" property="platformName" />
        <result column="platform_id" property="platformId" />
        <result column="account" property="account" />
        <result column="password" property="password" />
        <result column="app_key" property="appKey" />
        <result column="app_secret" property="appSecret" />
        <result column="app_path" property="appPath" />
        <result column="remark" property="remark" />
        <result column="create_time" property="createTime" />
        <result column="update_time" property="updateTime" />
        <result column="del_flag" property="delFlag" />
    </resultMap>
    <sql id="Base_Column_List">
        id, type, platform_name, platform_id, account, password, app_key,
        app_secret, app_path, remark, create_time, update_time, del_flag
    </sql>
</mapper>
ruoyi-service/ruoyi-other/src/main/resources/sql/t_platform_config.sql
New file
@@ -0,0 +1,21 @@
-- ----------------------------
-- 增加各平台交互配置表
create table t_platform_config
(
    id                 bigint auto_increment comment '主键ID'
        primary key,
    type integer  NULL COMMENT '类型(1:光伏,2:储能)',
    platform_name varchar(100)  null comment '所属平台(利普芯、欧力特)',
    platform_id Integer  COMMENT '所属平台ID',
    account VARCHAR(200) COMMENT '账号',
    password VARCHAR(500) COMMENT '密码',
    app_key VARCHAR(500) COMMENT '平台KEY',
    app_secret VARCHAR(500) COMMENT '平台密钥',
    app_path VARCHAR(500) COMMENT '平台请求地址',
    remark VARCHAR(500) COMMENT '备注',
    create_time        datetime                      null comment '创建时间',
    update_time        datetime                      null comment '更新时间',
    del_flag           char           default '0'    not null comment '删除标志(0存在 2删除)'
)
    comment '光伏储能配置表' collate = utf8mb4_bin;
ruoyi-service/ruoyi-other/src/test/java/com/ruoyi/other/controller/TPlatformConfigControllerTest.java
New file
@@ -0,0 +1,75 @@
package com.ruoyi.other.controller;
import com.ruoyi.other.domain.TPlatformConfig;
import com.ruoyi.other.service.TPlatformConfigService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
class TPlatformConfigControllerTest {
    private TPlatformConfigService service;
    private MockMvc mvc;
    @BeforeEach
    void setUp() {
        service = Mockito.mock(TPlatformConfigService.class);
        mvc = MockMvcBuilders.standaloneSetup(new TPlatformConfigController(service)).build();
    }
    @Test
    @SuppressWarnings({"rawtypes", "unchecked"})
    void bindsConfigArrayInsideObject() throws Exception {
        mvc.perform(post("/platform-config/saveBatch")
                .contentType(MediaType.APPLICATION_JSON)
                .content("{\"configs\":[{\"id\":null,\"type\":1,\"platformName\":\"科士达\",\"account\":\"demo\"},"
                        + "{\"id\":8,\"type\":2,\"platformName\":\"华为\"}]}"))
                .andExpect(status().isOk()).andExpect(jsonPath("$.code").value(200));
        ArgumentCaptor<List> captor = ArgumentCaptor.forClass(List.class);
        Mockito.verify(service).saveBatchConfigs(captor.capture());
        List<TPlatformConfig> configs = captor.getValue();
        assertEquals(2, configs.size());
        assertEquals("科士达", configs.get(0).getPlatformName());
        assertEquals("demo", configs.get(0).getAccount());
        assertEquals(Long.valueOf(8), configs.get(1).getId());
        assertEquals(Integer.valueOf(2), configs.get(1).getType());
    }
    @Test
    void rejectsMissingOrNullConfigs() throws Exception {
        for (String body : new String[]{"{}", "{\"configs\":null}"}) {
            mvc.perform(post("/platform-config/saveBatch")
                    .contentType(MediaType.APPLICATION_JSON).content(body))
                    .andExpect(status().isBadRequest());
        }
        Mockito.verifyNoInteractions(service);
    }
    @Test
    void rejectsEmptyConfigs() throws Exception {
        mvc.perform(post("/platform-config/saveBatch")
                .contentType(MediaType.APPLICATION_JSON).content("{\"configs\":[]}"))
                .andExpect(status().isBadRequest());
        Mockito.verifyNoInteractions(service);
    }
    @Test
    void rejectsBareArrayForObjectContract() throws Exception {
        mvc.perform(post("/platform-config/saveBatch")
                .contentType(MediaType.APPLICATION_JSON).content("[{\"type\":1}]"))
                .andExpect(status().isBadRequest());
        Mockito.verifyNoInteractions(service);
    }
}
ruoyi-service/ruoyi-other/src/test/java/com/ruoyi/other/service/impl/TPlatformConfigServiceImplTest.java
New file
@@ -0,0 +1,166 @@
package com.ruoyi.other.service.impl;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.ruoyi.common.core.exception.ServiceException;
import com.ruoyi.other.domain.TPlatformConfig;
import com.ruoyi.other.mapper.TPlatformConfigMapper;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.springframework.test.util.ReflectionTestUtils;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.anyCollection;
import static org.mockito.ArgumentMatchers.anyInt;
/** 配置保存边界及表映射测试,不启动外部服务。 */
class TPlatformConfigServiceImplTest {
    @BeforeAll
    static void initTableInfo() {
        TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""),
                TPlatformConfig.class);
    }
    @Test
    void mapsLogicalDeletionAndProtectsCreationTime() {
        TableInfo table = TableInfoHelper.getTableInfo(TPlatformConfig.class);
        assertEquals("t_platform_config", table.getTableName());
        assertEquals(Long.class, table.getKeyType());
        assertEquals("0", table.getLogicDeleteFieldInfo().getLogicNotDeleteValue());
        assertEquals("2", table.getLogicDeleteFieldInfo().getLogicDeleteValue());
        assertEquals(FieldStrategy.NEVER, table.getFieldList().stream()
                .filter(field -> "create_time".equals(field.getColumn()))
                .findFirst().get().getUpdateStrategy());
    }
    @Test
    @SuppressWarnings({"rawtypes", "unchecked"})
    void listsAllWithoutPaginationInIdOrder() {
        TPlatformConfigMapper mapper = Mockito.mock(TPlatformConfigMapper.class);
        List<TPlatformConfig> rows = Arrays.asList(config(1L), config(2L));
        Mockito.when(mapper.selectList(Mockito.any())).thenReturn(rows);
        TPlatformConfigServiceImpl service = service(mapper);
        assertSame(rows, service.listAll());
        ArgumentCaptor<Wrapper> query = ArgumentCaptor.forClass(Wrapper.class);
        Mockito.verify(mapper).selectList(query.capture());
        assertEquals("ORDER BY id ASC", query.getValue().getSqlSegment().trim());
    }
    @Test
    void rejectsEmptyOrNullItemsBeforeWriting() {
        TPlatformConfigServiceImpl service = Mockito.spy(new TPlatformConfigServiceImpl());
        assertThrows(ServiceException.class, () -> service.saveBatchConfigs(null));
        assertThrows(ServiceException.class, () -> service.saveBatchConfigs(Collections.emptyList()));
        assertThrows(ServiceException.class,
                () -> service.saveBatchConfigs(Arrays.asList(config(null), null)));
        Mockito.verify(service, Mockito.never()).saveOrUpdateBatch(anyCollection(), anyInt());
    }
    @Test
    void rejectsInvalidTypeAndDuplicateIdsBeforeWriting() {
        TPlatformConfigServiceImpl service = Mockito.spy(new TPlatformConfigServiceImpl());
        TPlatformConfig invalid = config(null);
        invalid.setType(3);
        assertThrows(ServiceException.class,
                () -> service.saveBatchConfigs(Collections.singletonList(invalid)));
        assertThrows(ServiceException.class,
                () -> service.saveBatchConfigs(Arrays.asList(config(1L), config(1L))));
        assertThrows(ServiceException.class,
                () -> service.saveBatchConfigs(Collections.singletonList(config(0L))));
        Mockito.verify(service, Mockito.never()).saveOrUpdateBatch(anyCollection(), anyInt());
    }
    @Test
    void rejectsMissingOrDeletedIdBeforeWritingAnyNewRows() {
        TPlatformConfigServiceImpl service = Mockito.spy(new TPlatformConfigServiceImpl());
        Mockito.doReturn(Collections.emptyList()).when(service).listByIds(anyCollection());
        assertThrows(ServiceException.class,
                () -> service.saveBatchConfigs(Arrays.asList(config(null), config(5L))));
        Mockito.verify(service, Mockito.never()).saveOrUpdateBatch(anyCollection(), anyInt());
    }
    @Test
    void savesMixedBatchAndIgnoresClientManagedMetadata() {
        TPlatformConfigServiceImpl service = Mockito.spy(new TPlatformConfigServiceImpl());
        Mockito.doReturn(Collections.singletonList(config(5L))).when(service).listByIds(anyCollection());
        Mockito.doReturn(true).when(service).saveOrUpdateBatch(anyCollection(), anyInt());
        TPlatformConfig created = config(null);
        TPlatformConfig updated = config(5L);
        updated.setCreateTime(LocalDateTime.of(2000, 1, 1, 0, 0));
        updated.setUpdateTime(updated.getCreateTime());
        updated.setDelFlag("2");
        List<TPlatformConfig> rows = Arrays.asList(created, updated);
        service.saveBatchConfigs(rows);
        Mockito.verify(service).saveOrUpdateBatch(Mockito.same(rows), anyInt());
        assertNull(created.getId());
        assertEquals(Long.valueOf(5), updated.getId());
        assertEquals("0", created.getDelFlag());
        assertEquals("0", updated.getDelFlag());
        assertNull(updated.getCreateTime());
        assertNull(updated.getUpdateTime());
    }
    @Test
    void acceptsNullableTypeAsDefinedBySchema() {
        TPlatformConfigServiceImpl service = Mockito.spy(new TPlatformConfigServiceImpl());
        Mockito.doReturn(true).when(service).saveOrUpdateBatch(anyCollection(), anyInt());
        TPlatformConfig row = config(null);
        row.setType(null);
        service.saveBatchConfigs(Collections.singletonList(row));
        Mockito.verify(service).saveOrUpdateBatch(anyCollection(), anyInt());
    }
    @Test
    void propagatesBatchFailuresSoTransactionCanRollBack() {
        TPlatformConfigServiceImpl service = Mockito.spy(new TPlatformConfigServiceImpl());
        Mockito.doReturn(false).when(service).saveOrUpdateBatch(anyCollection(), anyInt());
        assertThrows(ServiceException.class,
                () -> service.saveBatchConfigs(Collections.singletonList(config(null))));
        IllegalStateException failure = new IllegalStateException("database failure");
        Mockito.doThrow(failure).when(service).saveOrUpdateBatch(anyCollection(), anyInt());
        assertSame(failure, assertThrows(IllegalStateException.class,
                () -> service.saveBatchConfigs(Collections.singletonList(config(null)))));
    }
    @Test
    void doesNotIncludeSecretsInToString() {
        TPlatformConfig row = config(null);
        row.setPassword("private-password");
        row.setAppKey("private-key");
        row.setAppSecret("private-secret");
        assertTrue(!row.toString().contains("private-"));
    }
    private TPlatformConfigServiceImpl service(TPlatformConfigMapper mapper) {
        TPlatformConfigServiceImpl service = new TPlatformConfigServiceImpl();
        ReflectionTestUtils.setField(service, "baseMapper", mapper);
        return service;
    }
    private TPlatformConfig config(Long id) {
        TPlatformConfig config = new TPlatformConfig();
        config.setId(id);
        config.setType(1);
        config.setPlatformName("利普芯");
        return config;
    }
}