smartclient(barcode system)

本文介绍了一个基于条码的领料管理系统,详细解释了如何通过输入条码进行领料单头与单行数据的查找、上传、打印、删除和更新操作。
恩 不错不错 真的是一点一点写出来的,good !
//------------------------设置fields,及fields所属类型------------------------------
 headField=[
	  	{name:"recId",title:"主键",primaryKey:true},
	  	{name:"refId",title:"单号",canEdit:true},
	  	{name:"pickListNum",title:"领料预算表"},
	  	{name:"createdBy",title:"创建人"},
	  	{name:"createdDate",title:"创建日期"}
  ];
  
lineField=[
        {name:"recId", title:"主键",primaryKey:true,width:150},
        {name:"exFactoryNo", title:"出场单号",width:150},
        {name:"itemId",title:"物料编号",width:150},
        {name:"inputQty",title:"总数量",width:150},
		{name:"inputUnit",title:"单位",width:150},
		{name:"inputWeight",title:"总重量",width:150},
        {name:"levelQty",title:"对淮数",width:150},
        {name:"containerType",title:"箱类型",width:150},
        {name:"containerQty",title:"箱数量",width:150},
        {name:"mouldId",title:"洋行模号",width:150},
        {name:"mouldRefNum",title:"模厂模号",width:150},
        {name:"mouldVersion",title:"模版本",width:150},
        {name:"frmWarehouse",title:"由仓库",width:150},
        {name:"frmLocation",title:"由位置",width:150},
        {name:"toWarehouse",title:"至仓库",width:150},
        {name:"toLocation",title:"至位置",width:150},
        {name:"frmProjId",title:"由项目",width:150},
        {name:"frmSKU",title:"由SKU",width:150},
        {name:"frmBatchId",title:"由批号",width:150},
        {name:"toProjId",title:"至项目",width:150},
        {name:"toSKU",title:"至SKU",width:150},
        {name:"toBatchId",title:"至批号",width:150},
        {name:"pickListNum",title:"领料预算表",width:150},
        {name:"remarks",title:"备注",width:150}
];
//--------------------------------数据源------------------------------------------------------------	    
isc.DataSource.create({
	ID:"HeadData",
	dataFormat:"json",
	recordXPath:"data",
	fields:headField,
	
	operationBindings:[ 
	        {operationType:"fetch", dataURL:"BarCode/findAllBarcodeHead.action" },
	        {operationType:"remove",dataURL:"BarCode/deleteBarcodeHead.action"},
	        {operationType:"update",dataURL:"BarCode/UpdateBarcodeHead.action"}
	]
});
  
  isc.DataSource.create({ 
   	    ID:"lineData",
	    dataFormat:"json",  
	    recordXPath:"data",  
	    fields:lineField,
	    operationBindings:[ 
	      //  {operationType:"fetch", dataURL:"BarCode/findAllBarcodeLine.action" },
	        {operationType:"fetch",dataURL:"BarCode/findByFKRecId.action"},
	       // {operationType:"add", dataURL:"TreeAndList/saveInfo.action"},
	        {operationType:"update",  dataURL:"BarCode/updateBarcodeLine.action" },
	        {operationType:"remove", dataURL:"BarCode/deleteByRecId.action" }
	    ]
});

isc.ListGrid.create({
		ID:"BarcodeHead",
	    width:950,
	    showRowNumbers:true,
	    recordClick:"this.findItems(this.getSelectedRecord().recId)",
	    height:155,
		//autoFetchData:true,
		fields:headField, 
		dataSource:"HeadData", //数据来源 
	    alternateRecordStyles:true,
	    findItems : function (recId) {
	    	var findValues;
	    	 //var recId = BarcodeHead.getSelectedRecord().recId;
	    	//isc.say('recId--->'+recId);
	    	findValues = {PickListTableRecId:recId}; //設置PickListTableRecId值為recId的值
	    	LineList.filterData(findValues); //like fechData function
   	    }
});
 
isc.ListGrid.create({ 
	    ID: "LineList",
	    height:320,
	    showRowNumbers:true,
	    dataSource:"lineData",
	    //recordClick:"isc.say('您单击listgrid一次')",
	  //  autoFetchData: true,//自动获取数据 
	    canEdit: true, //可编辑grid
	    autoSaveEdits: true, //自动保存所编辑的内容 
	    fields:lineField, //对应的字段
	    alternateRecordStyles:true,
	    canEdit:true
});	
//*************************************条码输入框及事件处理*********************************************************
  	isc.SearchForm.create({
		ID:"findForm",
		left:20,
		autoFocus:true,//设置自动获取光标
		width:"120",
		fields:[{name: "barcode", type:"text", title:"barcode",width:250,height:25}],
		
		itemKeyPress:function(item,keyName,characterName){
			var itemName=this.getItems()[0].getFieldName();
			var barcode = this.getItems()[0].getValue(); // 获取输入文本框中的值
			var focusNode = this;
			var itemObj=this.getItem(itemName);
			if((barcode !== null) && (keyName == 'Enter')){  //如果条码不为空 and 单击回车键
				RPCManager.sendRequest({
					paramsOnly:true,
					//actionURL: "BarCode/insertBarcode.action", //增加条码
					actionURL:"Barcode/IsHavePickListNum.action",//检查条码
					params:{inputbarcode:barcode},
					callback:function(data) {focusNode.myCallBack(data);}
				});
			}
		},
		myCallBack:function(data) {
			var focusNode = this;
			var msg=data.data.split(",")[0].split(":")[1];
			var boo = data.data.split(",")[2].split(":")[1];
			var barcode = data.data.split(",")[3].split(":")[1];
			var pickListNum = data.data.split(",")[1].split(":")[1];
			var line = "barcodeline";
			var head = "barcodehead";
			console.log(data.data);
			if(barcode.contains(head)){
				if(boo){
					console.log(pickListNum);
					isc.ask("温馨提示:" + msg+"领料预算表:"+pickListNum,focusNode.addBarcodeHead);
				}else{
					isc.ask("记录不存在 可以添加"+msg,focusNode.addBarcodeHead);
				}//BarcodeHead.clearCriteria();//清除过滤器中的数据
			}else if (barcode.contains(line)){
				/*var recId = data.data.split(",")[2].split(":")[1].substring(0,2); 
				console.log("recId is --->"+recId);
				var FKRecId = { PickListTableRecId:recId };
				LineList.filterData(FKRecId);
				isc.say("温馨提示:" + msg );
				*/
				if(boo){
					var itemId = data.data.split(",")[4].split(":")[1];
					isc.ask("溫馨提示:"+msg+"領料預算表:"+pickListNum+"物料編號:"+itemId,focusNode.addBarcodeHead);
				}else{
					focusNode.addBarcodeHead();
				}
			}else{
				isc.say("温馨提示 :" + msg );
			}
			//this.getFields()[0].setValue()=" "; //清空条码框
		},
		addBarcodeHead:function(value){
			if(value){
			var focusNode = this;
				var bar = findForm.getField("barcode").getValue();
				//findForm.getField("barcode").setValue()="";
				console.log(bar);
				RPCManager.sendRequest({
					paramsOnly:true,
					actionURL:"BarCode/insertBarcode.action",
					params:{inputbarcode:bar},
					callback:function(data){
						var msg = data.data.split(",")[0].split(":")[1];
						var barcode = data.data.split(",")[0].split(":")[1];
						var line = "barcodeline";
						var head = "barcodehead";
						if(barcode.contains(head)){
							BarcodeHead.filterData();
							isc.say("温馨提示"+msg);
						}else if (barcode.contains(line)){
							var recId = data.data.split(",")[2].split(":")[1].substring(0,2);
							console.log("recId is --->"+recId);
							var FKRecId = { PickListTableRecId:recId };
							LineList.filterData(FKRecId);
							isc.say("温馨提示:" + msg );
						}else{
							isc.say("温馨提示:"+msg);
						}	
					}
				})
			}else{
				isc.say("您取消添加单头");
			}
			findForm.getFields()[0].setValue()=" "; //清空条码框
		}
	})

//******************************************单头事件按钮***************************************************************
	isc.HLayout.create({
         ID:"buttonLayout",
         width:"100%", height:1,
         membersMargin: 30,
         members: [
             isc.IButton.create({
                 ID: "upload",
                 width:"120",
                 height:25,
                 title: "上  载",
                 click:function(){
                	 var focusNode=this;
	                 if((!BarcodeHead.getSelectedRecord())){
	                 	isc.say("请选择上载记录");
	                 }else{
	                 	var refId = BarcodeHead.getSelectedRecords()[0].refId;
                     	isc.ask("确定上载领料单号:"+refId+"吗?",focusNode.myCallBack);
                     }
                },
                myCallBack:function(value) {
                 var recId = BarcodeHead.getSelectedRecord().recId; // 获取当前选中的主键
	                if(value){
		                 RPCManager.sendRequest({
							paramsOnly:true,
							actionURL: "Barcode/uploadppcPickList.action",
							params:{a:recId},
							callback:function(data) {
							  var msg=data.data.split(",")[0].split(":")[1];
							  var recId = data.data.split(",")[2].split(":")[1].substring(0,2);
							  BarcodeHead.filterData();//自动刷新领料单行数据
							  var FKRecId = { PickListTableRecId:recId };
							  LineList.filterData(FKRecId); //上载成功后自动刷新数据
							  alert("温馨提示:" + msg);
							}
						});
				   }else{};
			    } 
             }),
             isc.IButton.create({
                 ID: "print",
                 width:"120",
                 height:25,
                 title: "列  印",
                 click: "BarcodeHead.addSummaryField();"
             }),
             isc.IButton.create({
             	ID:"delete",
             	width:"120",
             	height:25,
             	title:"删  除",
             	click:function(){
	             	var focusNode = this;
	             	if((!BarcodeHead.getSelectedRecord())){
	             		isc.say('请选择要删除的记录');
	             	}else{
	             		var table = BarcodeHead.getSelectedRecords()[0].pickListNum;
	             		isc.ask("确认删除领料预算表:"+table+"吗?",focusNode.Mycallback); //对是否做出相应的处理 
	             	}
             	},
             	Mycallback: function(value){
             	console.log(value);
             		 if(value){
	             		BarcodeHead.removeSelectedData();
	             		isc.say('删除成功!');
             		}else if(value=="" || value == null){
             		}else{
             			isc.say('删除失败,请重新操作!');
             		} 
             	}
             }),
             isc.IButton.create({
                 ID: "updateClumn",
                 width:"120",
                 height:25,
                 title: " 更 新 栏 位",
                 click: function () { 
                 var focusNode = this;
	                 if((!BarcodeHead.getSelectedRecord())){
	                 	isc.say('请选择要更新的栏位');
	                 }else{
	                 	isc.askForValue("请输入要修改的单号",focusNode.updateInfo);
	                 }
                 },
                 updateInfo:function(value){
                 if(value){
	                 var Id = BarcodeHead.getSelectedRecords()[0].recId;
	                 var focus = this;
	                 RPCManager.sendRequest({
						paramsOnly:true,
						actionURL: "BarCode/UpdateBarcodeHead.action",
						params:{refId:value,recId:Id},
						callback:function(data){
							var msg=data.data.split(",")[0].split(":")[1];
							BarcodeHead.filterData();
	                		isc.say(msg);}
					 });
				 }else if(value ==null || value ==""){} 
                 }
             })
         ]
     });
//******************************************单行事件按钮***************************************************************	
        isc.HLayout.create({ 
            ID:"buttonLayout2",
            width:"100%", height:10,
            membersMargin: 20,
            members: [
                isc.IButton.create({
                	ID:"delete2",
                	width:"120",
                	height:25,
                	title:"删除信息",
                	click:function(){
                		if(!LineList.getSelectedRecord()){
                			isc.say('请选择要删除的记录');
                		}else{
                		var itemId = LineList.getSelectedRecords()[0].itemId;
                			isc.ask('确认删除物料编号:'+itemId+'?',this.deleteLine);
                		}
                	},
                	deleteLine:function(value){
	                	if(value){
	               	    	LineList.removeSelectedData();
							isc.say("删除成功!");
						}else if(value == "" || value== null){
							
						}else{
							isc.say('删除失败,请重新操作!');
						}
                	}
                }),
                isc.IButton.create({
                    ID: "updateClumn2",
                    width:"120",
                    height:25,
                    title: "更新栏位",
                    click:function(){
                    	if(!LineList.getSelectedRecord()){
                    		isc.say('请选择要更新的栏位');
                    	}else{
                    		updatelinewin.show()
                    	}
                    }
                })
            ]
        });
//********************************************系统首界面布局*************************************************************
    isc.DataView.create({
    	ID:"barcodeLayout",
	    autoDraw: true,
	    overflow: "hidden",
	    width: "100%",
	    height: "100%",
	    members: [
	    isc.HLayout.create({
	        membersMargin: 5,
	        padding:5,
	        vertical:"true",
	        members: [
	        isc.VLayout.create({
	        	ID:"barcode",
	        	padding:10,
	        	height:20,
	        	membersMargin:10,
	        	isGroup:true,
	        	members:[findForm],
	        	groupTitle:"请输入条码"
	        }),
	        isc.VLayout.create({
	        	ID:"barcodeHead",
	            padding:10,
	            membersMargin: 5,
	            members: [BarcodeHead,buttonLayout],
	            isGroup:true,
	            height:200,
	            groupTitle:"领料单头"
	        }),
	        isc.VLayout.create({
	            ID: "barcodeLine",
	            padding:10,
	            membersMargin: 5,
	            overflow: "auto",
	            members: [LineList,buttonLayout2],
	            isGroup:true,
	            groupTitle:"领料单行"
	        })]
	    })
	  ]
});
BarcodeHead.fetchData();


我现在正在写单元测试,其中,我发现源代码的 private OperationResponse<AddDeviceResult> addIpcDeviceByDeviceDiscovery(DiscoveredDeviceDTO discoveredDeviceDTO, DiscoverType discoverType, AddDiscoveredDeviceDTO request, String vmsId, Long siteId, String stok, Long rootSiteId) { // 获取IPC设备module_spec Optional<FunctionModuleSpecSection> moduleSpecSectionOptional; if (discoverType == DiscoverType.REMOTE) { moduleSpecSectionOptional = passThroughService.getModuleSpecInfo(discoveredDeviceDTO.getDevId()); } else { moduleSpecSectionOptional = getModuleSpecInfoByAutoDiscovery(stok); } if (!moduleSpecSectionOptional.isPresent()) { return OperationResponse.error(ResponseCode.PASSTHOUGH_TIME_OUT); } FunctionModuleSpecSection functionModuleSpecSection = moduleSpecSectionOptional.get(); // 解析capability Long devCapability = ipcInfoService.getCapabilityByIpcModuleSpec(functionModuleSpecSection); CapabilityObjectDO capabilityObject = ipcInfoService.getCapabilityObjectByIpcModuleSpec( functionModuleSpecSection); SmartDataCapability smartDataCapability; if (discoverType == DiscoverType.REMOTE) { smartDataCapability = deviceSmartDataCapabilityService .getSmartDataCapabilityByIpcModuleSpec(functionModuleSpecSection, discoveredDeviceDTO.getDevId(), null); } else { smartDataCapability = getSmartDataCapabilityByAutoDiscovery(stok, functionModuleSpecSection); } // 获取智能分析开关状态 SmartAnalysisModeDO smartAnalysisModeDO; if (discoverType == DiscoverType.REMOTE) { smartAnalysisModeDO = ipcSmartAnalysisModeService.getDirectIpcSmartAnalysisMode( discoveredDeviceDTO.getDevId(), smartDataCapability).getResult(); } else { smartAnalysisModeDO = getSmartAnalysisModeByAutoDiscovery(stok, smartDataCapability); } // 添加至数据库 Optional<DeviceDO> optionalDevice = deviceInternalRepository.findByDevId(discoveredDeviceDTO.getDevId()); Integer onlineStatusInt = deviceConnectorService.getOnlineStatus(discoveredDeviceDTO.getDevId()); OnlineStatus onlineStatus = OnlineStatus.findByCode(onlineStatusInt); onlineStatus = OnlineStatus.ONLINE.equals(onlineStatus) ? onlineStatus : OnlineStatus.ADDING; log.debug("add ipc get onlineStatus {}", onlineStatus); // Ipc不存在,则直接保存该Ipc进数据库中 DeviceDO deviceDO = new DeviceDO().setDevId(discoveredDeviceDTO.getDevId()) .setId(optionalDevice.map(DeviceDO::getId).orElse(null)) .setDeviceName(discoveredDeviceDTO.getDeviceName()) .setDeviceModel(discoveredDeviceDTO.getDeviceModel()) .setDeviceType(discoveredDeviceDTO.getDeviceType()) .setFirmwareVersion(discoveredDeviceDTO.getFirmwareVersion()) .setHardwareVersion(discoveredDeviceDTO.getHardwareVersion()) .setMacAddress(discoveredDeviceDTO.getMacAddress()) .setMacAddressList( getMacAddressList(discoveredDeviceDTO.getMacAddress(), functionModuleSpecSection)) .setIpAddressString(discoveredDeviceDTO.getIpAddressString()) .setOnlineStatus(discoverType == DiscoverType.REMOTE ? OnlineStatus.ONLINE : onlineStatus) .setLicenseStatus(LicenseStatusEnum.UNACTIVE) .setVmsId(vmsId) .setSiteId(siteId) .setRootSiteId(rootSiteId) .setChannel(Constant.DEFAULT_IPC_CHANNEL_NUM) .setUsername(request.getUsername()) .setPassword(request.getPassword()) .setCapability(devCapability) .setCapabilityObject(capabilityObject) .setSmartDataCapability(smartDataCapability) .setSmartAnalysisMode(smartAnalysisModeDO) .setSupportFirmwareVersion(discoveredDeviceDTO.getSupportDeviceFirmwareVersion()) // 默认为我司设备 .setVender(DeviceConstant.TP_LINK_VENDER) // 默认开启离线报警 .setOfflineAlarm(true) // 默认同步site的时区夏令时 .setFollowSiteTime(request.getFollowSiteTime()) .setBarCode(DeviceInfoUtils.getFormatBarCode(discoveredDeviceDTO.getQrCode())); SmartDetectionCapability smartDetectionCapability; if (discoverType == DiscoverType.REMOTE) { smartDetectionCapability = smartDetectionCapabilityService.getSmartDetectionCapability( discoveredDeviceDTO.getDevId()); } else { smartDetectionCapability = getSmartDetectionCapability(stok); } if (log.isDebugEnabled()) { log.debug("get device smart detection capability, devId {}, capability {}.", CustomPIIMaskUtil.encrypt(discoveredDeviceDTO.getDevId()), smartDetectionCapability); } if (!Objects.isNull(smartDetectionCapability)) { deviceDO.setSmartDetectionCapability(smartDetectionCapability.getCapabilityList()); } Long id = ipcRepository.save(deviceDO); deviceDO.setId(id); log.info("system->device->add ipc device {} to site {}", discoveredDeviceDTO.getDeviceName(), siteId); if (OnlineStatus.ONLINE.equals(onlineStatus)) { // 先连接再添加,发布上线事件 deviceStatusChangePublisher.publish(deviceDO); } return OperationResponse.ok(new AddDeviceResult().setOnlineStatus(onlineStatus).setDeviceId(id) .setDeviceType(DeviceType.IPC).setChannelNum((short) 1).setAddedDevice(optionalDevice.isPresent())); }这个方法的 if (discoverType == DiscoverType.REMOTE) { smartAnalysisModeDO = ipcSmartAnalysisModeService.getDirectIpcSmartAnalysisMode( discoveredDeviceDTO.getDevId(), smartDataCapability).getResult(); }会在我的单测代码中报错,单测代码为:/* * Copyright (c) 2025, TP-Link. All rights reserved. */ package com.tplink.cdd.vms.manager.local.domain.device.manage; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.function.Supplier; import cn.hutool.core.map.MapUtil; import com.github.rholder.retry.Retryer; import org.jetbrains.annotations.NotNull; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor; import org.powermock.modules.junit4.PowerMockRunner; import org.powermock.modules.junit4.PowerMockRunnerDelegate; import org.powermock.reflect.Whitebox; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; import com.tplink.cdd.component.datatrack.common.util.JsonUtil; import com.tplink.cdd.device.gateway.connector.api.DeviceConnectorInternalApiService; import com.tplink.cdd.device.gateway.connector.api.dto.CheckDeviceDTO; import com.tplink.cdd.device.gateway.connector.api.dto.ConnectorConfigDTO; import com.tplink.cdd.vms.common.constants.DeviceType; import com.tplink.cdd.vms.common.constants.DiscoverDeviceType; import com.tplink.cdd.vms.common.constants.DiscoverType; import com.tplink.cdd.vms.common.constants.OnlineStatus; import com.tplink.cdd.vms.common.constants.RedisKey; import com.tplink.cdd.vms.common.constants.ResponseCode; import com.tplink.cdd.vms.common.constants.websocket.StompStandardConstant; import com.tplink.cdd.vms.common.dto.OperationResponse; import com.tplink.cdd.vms.common.dto.UserDTO; import com.tplink.cdd.vms.common.util.DeviceUtil; import com.tplink.cdd.vms.common.util.VersionUtils; import com.tplink.cdd.vms.excutor.UniqueTaskExecutor; import com.tplink.cdd.vms.manager.api.external.internal.device.dto.SmartDataCapability; import com.tplink.cdd.vms.manager.api.web.device.dto.AddDeviceDTO; import com.tplink.cdd.vms.manager.api.web.device.dto.AddDiscoveredDeviceDTO; import com.tplink.cdd.vms.manager.api.web.device.dto.BatchAddDeviceV2DTO; import com.tplink.cdd.vms.manager.api.web.device.dto.SmartAnalysisRespDTO; import com.tplink.cdd.vms.manager.api.web.result.dto.WebSocketResultDTO; import com.tplink.cdd.vms.manager.core.domain.device.event.publisher.DeviceAddPublisher; import com.tplink.cdd.vms.manager.core.domain.device.event.publisher.DeviceStatusChangePublisher; import com.tplink.cdd.vms.manager.core.domain.device.manage.DeviceSmartDataCapabilityService; import com.tplink.cdd.vms.manager.core.domain.device.manage.IpcInfoService; import com.tplink.cdd.vms.manager.core.domain.device.manage.IpcSmartAnalysisModeService; import com.tplink.cdd.vms.manager.core.domain.device.manage.NvrInfoService; import com.tplink.cdd.vms.manager.core.domain.device.manage.NvrLldpService; import com.tplink.cdd.vms.manager.core.domain.device.manage.PassThroughService; import com.tplink.cdd.vms.manager.core.domain.device.manage.SiteChannelLimitService; import com.tplink.cdd.vms.manager.core.domain.device.manage.SmartDetectionCapabilityService; import com.tplink.cdd.vms.manager.core.domain.device.manage.sync.NvrChildSyncBasicService; import com.tplink.cdd.vms.manager.core.domain.device.passthrough.SolarPassthroughService; import com.tplink.cdd.vms.manager.core.domain.retry.RetryBuilder; import com.tplink.cdd.vms.manager.core.domain.site.manage.QuerySiteService; import com.tplink.cdd.vms.manager.core.port.cache.DeviceAddMacConflictResultCache; import com.tplink.cdd.vms.manager.core.port.cache.WebSocketResultCacheService; import com.tplink.cdd.vms.manager.core.port.dto.passthrough.FunctionModuleSpecSection; import com.tplink.cdd.vms.manager.core.port.dto.passthrough.LldpResponse; import com.tplink.cdd.vms.manager.core.port.dto.passthrough.RetFunctionIncludingModuleSpec; import com.tplink.cdd.vms.manager.core.port.dto.passthrough.capability.SmartDetectionCapability; import com.tplink.cdd.vms.manager.core.port.dto.passthrough.capability.SmartDetectionCapabilityDTO; import com.tplink.cdd.vms.manager.core.port.dto.passthrough.capability.SmartDetectionCapabilityResponse; import com.tplink.cdd.vms.manager.core.port.dto.passthrough.solar.SolarResponse; import com.tplink.cdd.vms.manager.core.port.dto.passthrough.solar.response.SolarFwResponse; import com.tplink.cdd.vms.manager.core.port.dto.passthrough.solar.response.SolarGetSystemStatusResponse; import com.tplink.cdd.vms.manager.core.port.dto.passthrough.solar.response.SystemStatusDTO; import com.tplink.cdd.vms.manager.core.port.eventcenter.device.DeviceBatchProcessPublisher; import com.tplink.cdd.vms.manager.core.port.eventcenter.device.DeviceBatchProcessPublisherV2; import com.tplink.cdd.vms.manager.core.port.slp.DeviceSlpInterface; import com.tplink.cdd.vms.manager.core.port.slp.SolarMessageManager; import com.tplink.cdd.vms.manager.core.port.util.DeviceInfoUtils; import com.tplink.cdd.vms.manager.discover.cache.AutoDiscoverCache; import com.tplink.cdd.vms.manager.discover.cache.ManualDiscoverCache; import com.tplink.cdd.vms.manager.discover.cache.RemoteDiscoverCache; import com.tplink.cdd.vms.manager.discover.constant.FactoryDefine; import com.tplink.cdd.vms.manager.discover.dto.AuthAndAccessResponse; import com.tplink.cdd.vms.manager.discover.dto.DiscoveredDeviceDTO; import com.tplink.cdd.vms.manager.discover.service.AuthenticationService; import com.tplink.cdd.vms.manager.discover.util.HttpRequestUtil; import com.tplink.cdd.vms.manager.discover.util.SolarInterfaceUtil; import com.tplink.cdd.vms.manager.repository.api.device.DeviceInternalRepository; import com.tplink.cdd.vms.manager.repository.api.device.DeviceRepository; import com.tplink.cdd.vms.manager.repository.api.device.IpcRepository; import com.tplink.cdd.vms.manager.repository.api.device.NvdRepository; import com.tplink.cdd.vms.manager.repository.api.device.NvrRepository; import com.tplink.cdd.vms.manager.repository.api.device.SolarRepository; import com.tplink.cdd.vms.manager.repository.api.device.domain.CapabilityObjectDO; import com.tplink.cdd.vms.manager.repository.api.device.domain.DeviceDO; import com.tplink.cdd.vms.manager.repository.api.device.domain.LldpDO; import com.tplink.smb.component.cache.api.CacheService; import com.tplink.smb.component.lock.api.LockService; import com.tplink.smb.log.component.client.api.LogApiService; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.powermock.api.mockito.PowerMockito.doNothing; import static org.powermock.api.mockito.PowerMockito.when; /** * Description of this file * * @author Li Tailong * @version 1.0 * @since 2025/10/21 */ @DirtiesContext @RunWith(PowerMockRunner.class) @PowerMockRunnerDelegate(SpringRunner.class) @PrepareForTest({VersionUtils.class, AuthenticationService.class, DeviceUtil.class, OnlineStatus.class, SolarMessageManager.class, DeviceInfoUtils.class, DeviceSlpInterface.class, SolarInterfaceUtil.class, HttpRequestUtil.class}) @SuppressStaticInitializationFor("com.example.DeviceUtil") @PowerMockIgnore({"javax.*.*", "com.sun.org.apache.xerces.*", "org.w3c.dom.*", "org.xml.*"}) public class AddDeviceV2ServiceTest { @InjectMocks private AddDeviceV2Service addDeviceV2Service; @Mock private LockService lockService; @Mock private RetryBuilder retryBuilder; @Mock private ThreadPoolTaskExecutor executor; @Mock private UniqueTaskExecutor iframeExecutor; @Mock private DiscoverService discoverService; @Mock private DeviceRepository deviceRepository; @Mock private AutoDiscoverCache autoDiscoverCache; @Mock private PassThroughService passThroughService; @Mock private DeviceBatchProcessPublisherV2 publisher; @Mock private ManualDiscoverCache manualDiscoverCache; @Mock private RemoteDiscoverCache remoteDiscoverCache; @Mock private DeviceConnectorInternalApiService deviceConnectorService; @Mock private DeviceAddMacConflictResultCache deviceAddMacConflictResultCache; @Mock private IpcRepository ipcRepository; @Mock private NvrRepository nvrRepository; @Mock private NvdRepository nvdRepository; @Mock private SolarRepository solarRepository; @Mock private LogApiService logApiService; @Mock private IpcInfoService ipcInfoService; @Mock private NvrInfoService nvrInfoService; @Mock private DeviceSmartDataCapabilityService deviceSmartDataCapabilityService; @Mock private SmartDetectionCapabilityService smartDetectionCapabilityService; @Mock private DeviceInternalRepository deviceInternalRepository; @Mock private SolarPassthroughService solarPassthroughService; @Mock private DeviceBatchProcessPublisher deviceBatchProcessPublisher; @Mock private CacheService cacheService; @Mock private QuerySiteService querySiteService; @Mock private SiteChannelLimitService siteChannelLimitService; @Mock private DeviceStatusChangePublisher deviceStatusChangePublisher; @Mock private DeviceAddPublisher deviceAddPublisher; @Mock private NvrChildSyncBasicService nvrChildSyncBasicService; @Mock private IpcSmartAnalysisModeService ipcSmartAnalysisModeService; @Mock private NvrLldpService nvrLldpService; @Mock private WebSocketResultCacheService webSocketResultCacheService; private Retryer<Object> statusCheckRetry; @Before public void setUp() { Whitebox.setInternalState(addDeviceV2Service, "webSocketResultCacheService", webSocketResultCacheService); Mockito.doAnswer(invocationOnMock -> ((Supplier<?>) invocationOnMock.getArgument(2)).get()) .when(lockService).doWithTryLock(anyString(), any(), any(), any()); Mockito.doAnswer(invocationOnMock -> ((Supplier<?>) invocationOnMock.getArgument(4)).get()) .when(lockService).doWithTryLock(anyString(), any(), anyLong(), any(), any(), any()); Mockito.doAnswer(invocationOnMock -> { ((Runnable) invocationOnMock.getArgument(0)).run(); return null; }).when(executor).execute(Mockito.any(Runnable.class)); Mockito.doAnswer(invocationOnMock -> { ((Runnable) invocationOnMock.getArgument(1)).run(); return null; }).when(iframeExecutor).execute(anyString(), Mockito.any(Runnable.class)); statusCheckRetry = new RetryBuilder().buildWithMilliseconds(20, 500); PowerMockito.mockStatic(VersionUtils.class); PowerMockito.mockStatic(AuthenticationService.class); PowerMockito.mockStatic(DeviceUtil.class); PowerMockito.mockStatic(OnlineStatus.class); PowerMockito.mockStatic(SolarMessageManager.class); PowerMockito.mockStatic(DeviceInfoUtils.class); PowerMockito.mockStatic(DeviceSlpInterface.class); PowerMockito.mockStatic(SolarInterfaceUtil.class); PowerMockito.mockStatic(HttpRequestUtil.class); } @Test public void testInit() { addDeviceV2Service.init(); } @Test public void testAddDiscoveredDevices() { AddDiscoveredDeviceDTO request = new AddDiscoveredDeviceDTO(); request.setUserId("userId"); request.setUuid("uuid"); request.setVmsId("vmsId"); request.setDiscoverType(DiscoverType.AUTO); request.setSelectAll(true); request.setSearchValue("model"); request.setUsername("username"); request.setPassword("password"); DiscoveredDeviceDTO discoveredDeviceDTO = getDiscoveredDeviceDTO(); Map<String, DiscoveredDeviceDTO> discoveredMap = new HashMap<>(); discoveredMap.put("devId", discoveredDeviceDTO); Mockito.when(autoDiscoverCache.getAutoDiscoveredResult()).thenReturn(discoveredMap); Mockito.when(querySiteService.getRootSiteId(any(), any())).thenReturn(1L); Mockito.when((cacheService.get(anyString(), any(), any()))).thenReturn("test"); Mockito.when(VersionUtils.compareDeviceVersion(any(), any())).thenReturn(false); addDeviceV2Service.addDiscoveredDevices(request); Mockito.when(VersionUtils.compareDeviceVersion(any(), any())).thenReturn(true); Mockito.when(discoverService.checkDeviceIsInDb(any(), any())).thenReturn(true); addDeviceV2Service.addDiscoveredDevices(request); Mockito.when(discoverService.checkDeviceIsInDb(any(), any())).thenReturn(false); // addSingleDevice ConnectorConfigDTO connectorConfigDTO = new ConnectorConfigDTO(); connectorConfigDTO.setDeviceConnectPort(80); AuthAndAccessResponse authAndAccessResponse = new AuthAndAccessResponse(0, "stok", "secLeft", 1000); AddDeviceResult addDeviceResult = new AddDeviceResult(); Mockito.when(deviceConnectorService.getConnectorConfig()).thenReturn(OperationResponse.ok(connectorConfigDTO)); Mockito.when(AuthenticationService.deviceAuthAndAccess(any())).thenReturn(authAndAccessResponse); Mockito.when(DeviceUtil.typeSwitch(any(), any(), any(), any(), any(), any())) .thenReturn(OperationResponse.ok(addDeviceResult)); Mockito.doNothing().when(webSocketResultCacheService).delete(anyString(), anyString()); Mockito.doNothing().when(webSocketResultCacheService).setValue(anyString(), anyString(), any()); addDeviceV2Service.addDiscoveredDevices(request); } @Test public void testBuildInitDeviceParam() throws InvocationTargetException, IllegalAccessException { AddDiscoveredDeviceDTO request = new AddDiscoveredDeviceDTO(); request.setInitUsername("initUsername"); request.setInitPassword("initPassword"); request.setResetPwdEmail("resetPwdEmail"); request.setLanguage("language"); request.setCountry("country"); request.setPowerLineFre("powerLineFre"); request.setTimeZone("timeZone"); request.setZoneId("zoneId"); request.setQuestion1("question1"); request.setQuestion2("question2"); request.setQuestion3("question3"); request.setAnswer1("answer1"); request.setAnswer2("answer2"); request.setAnswer3("answer3"); Method buildInitDeviceParam = Whitebox.getMethod(AddDeviceV2Service.class, "buildInitDeviceParam", AddDiscoveredDeviceDTO.class); buildInitDeviceParam.setAccessible(true); buildInitDeviceParam.invoke(addDeviceV2Service, request); } @Test public void testAddNvrDeviceByDeviceDiscovery() throws InvocationTargetException, IllegalAccessException { DiscoveredDeviceDTO discoveredDeviceDTO = getDiscoveredDeviceDTO(); AddDiscoveredDeviceDTO request = getAddDiscoveredDeviceDTO(); FunctionModuleSpecSection functionModuleSpecSection = new FunctionModuleSpecSection(); functionModuleSpecSection.setMaxChannels("1"); Mockito.when(passThroughService.getModuleSpecInfo(any())).thenReturn(Optional.of(functionModuleSpecSection)); Mockito.when(nvrInfoService.getCapability(any())).thenReturn(1L); Mockito.when(nvrInfoService.getCapabilityObject(any())).thenReturn(new CapabilityObjectDO()); LldpDO lldpInfo = new LldpDO(); Mockito.when(nvrLldpService.getLldpInfo(any(), any())).thenReturn(lldpInfo); DeviceDO deviceDO = new DeviceDO(); deviceDO.setId(1L); deviceDO.setOnlineStatus(OnlineStatus.OFFLINE); Mockito.when(deviceInternalRepository.findByDevId(any())).thenReturn(Optional.of(deviceDO)); Mockito.when(nvrInfoService.getSmartDataCapability(any())).thenReturn(new SmartDataCapability()); Mockito.when(deviceConnectorService.getOnlineStatus(any())).thenReturn(1); Mockito.when(OnlineStatus.findByCode(anyInt())).thenReturn(OnlineStatus.ONLINE); Mockito.when(nvrRepository.save(any())).thenReturn(1L); Method addNvrDeviceByDeviceDiscovery = Whitebox.getMethod(AddDeviceV2Service.class, "addNvrDeviceByDeviceDiscovery", DiscoveredDeviceDTO.class, DiscoverType.class, AddDiscoveredDeviceDTO.class , String.class, Long.class, String.class, Long.class, Long.class); addNvrDeviceByDeviceDiscovery.setAccessible(true); addNvrDeviceByDeviceDiscovery.invoke(addDeviceV2Service, discoveredDeviceDTO, DiscoverType.REMOTE, request, "username", 1L, "password", 1L, 1L); } @Test public void testAddNvdDeviceByDeviceDiscovery() throws InvocationTargetException, IllegalAccessException { DiscoveredDeviceDTO discoveredDeviceDTO = getDiscoveredDeviceDTO(); AddDiscoveredDeviceDTO request = getAddDiscoveredDeviceDTO(); DeviceDO deviceDO = new DeviceDO(); deviceDO.setId(1L); deviceDO.setOnlineStatus(OnlineStatus.OFFLINE); Mockito.when(deviceInternalRepository.findByDevId(any())).thenReturn(Optional.of(deviceDO)); Mockito.when(deviceConnectorService.getOnlineStatus(any())).thenReturn(1); Mockito.when(OnlineStatus.findByCode(anyInt())).thenReturn(OnlineStatus.ONLINE); Mockito.when(nvdRepository.save(any())).thenReturn(1L); Method addNvdDeviceByDeviceDiscovery = Whitebox.getMethod(AddDeviceV2Service.class, "addNvdDeviceByDeviceDiscovery", DiscoveredDeviceDTO.class, DiscoverType.class, AddDiscoveredDeviceDTO.class , String.class, Long.class, Long.class); addNvdDeviceByDeviceDiscovery.setAccessible(true); addNvdDeviceByDeviceDiscovery.invoke(addDeviceV2Service, discoveredDeviceDTO, DiscoverType.REMOTE, request, "username", 1L, 1L); } @Test public void testAddSolarDeviceByDeviceDiscovery() throws InvocationTargetException, IllegalAccessException { DiscoveredDeviceDTO discoveredDeviceDTO = getDiscoveredDeviceDTO(); AddDiscoveredDeviceDTO request = getAddDiscoveredDeviceDTO(); Mockito.when(solarRepository.countByVmsId(any())).thenReturn(0L); SystemStatusDTO systemStatusDTO = new SystemStatusDTO(); Mockito.when(solarPassthroughService.getSystemStatus(any(), any())) .thenReturn(OperationResponse.ok(systemStatusDTO)); DeviceDO deviceDO = new DeviceDO(); deviceDO.setId(1L); deviceDO.setOnlineStatus(OnlineStatus.OFFLINE); Mockito.when(deviceInternalRepository.findByDevId(any())).thenReturn(Optional.of(deviceDO)); Mockito.when(solarRepository.save(any())).thenReturn(1L); Mockito.when(SolarMessageManager.getElectricNum(any())).thenReturn(1); Mockito.when((SolarMessageManager.getChargeStatus(any()))).thenReturn(true); Mockito.when(SolarMessageManager.getLowBattery(any())).thenReturn(true); Mockito.when(SolarMessageManager.getLowTemperature(any())).thenReturn(true); Mockito.when(SolarMessageManager.getHighTemperature(any())).thenReturn(true); Mockito.when(DeviceInfoUtils.getFormatBarCode(any())).thenReturn("test"); Method addSolarDeviceByDeviceDiscovery = Whitebox.getMethod(AddDeviceV2Service.class, "addSolarDeviceByDeviceDiscovery", DiscoveredDeviceDTO.class, DiscoverType.class, AddDiscoveredDeviceDTO.class , String.class, Long.class, AuthAndAccessResponse.class, Long.class); addSolarDeviceByDeviceDiscovery.setAccessible(true); addSolarDeviceByDeviceDiscovery.invoke(addDeviceV2Service, discoveredDeviceDTO, DiscoverType.REMOTE, request, "username", 1L , new AuthAndAccessResponse(0, "stok", "secLeft", 1000), 1L); } @Test public void testGetSmartDetectionCapability() throws InvocationTargetException, IllegalAccessException { UserDTO userDTO = new UserDTO(); userDTO.setUsername("username"); Mockito.when(DeviceSlpInterface.getSmartDetectionCapability()).thenReturn(userDTO); SmartDetectionCapabilityDTO smartDetectionCapabilityDTO = new SmartDetectionCapabilityDTO(); SmartDetectionCapabilityResponse smartDetectionCapabilityResponse = new SmartDetectionCapabilityResponse(); smartDetectionCapabilityResponse.setSmartDetectionCapabilityDTO(smartDetectionCapabilityDTO); String json = JsonUtil.bean2json(smartDetectionCapabilityResponse); Mockito.when(AuthenticationService.httpPostProcess(any(), any())).thenReturn(json); Method getSmartDetectionCapability = Whitebox.getMethod(AddDeviceV2Service.class, "getSmartDetectionCapability", String.class); getSmartDetectionCapability.setAccessible(true); getSmartDetectionCapability.invoke(addDeviceV2Service, "username"); } @Test public void testGetModuleSpecInfoByAutoDiscovery() throws InvocationTargetException, IllegalAccessException { Mockito.when(DeviceSlpInterface.getSlpModuleSpecInfo()).thenReturn("test"); RetFunctionIncludingModuleSpec retFunctionIncludingModuleSpec = new RetFunctionIncludingModuleSpec(); retFunctionIncludingModuleSpec.setErrorCode(0); String json = JsonUtil.bean2json(retFunctionIncludingModuleSpec); Mockito.when(AuthenticationService.httpPostProcess(any(), any())).thenReturn(json); Method getModuleSpecInfoByAutoDiscovery = Whitebox.getMethod(AddDeviceV2Service.class, "getModuleSpecInfoByAutoDiscovery", String.class); getModuleSpecInfoByAutoDiscovery.setAccessible(true); getModuleSpecInfoByAutoDiscovery.invoke(addDeviceV2Service, "username"); } @Test public void testGetBarCodeByAutoDiscovery() throws InvocationTargetException, IllegalAccessException { Mockito.when(DeviceSlpInterface.getSlpModuleSpecInfo()).thenReturn("test"); Object object = MapUtil.builder() .put("device_id", MapUtil.builder() .put(("basic_info"), MapUtil.builder().put("barcode", "test").build()).build()).build(); String json = JsonUtil.bean2json(object); Mockito.when(AuthenticationService.httpPostProcess(any(), any())).thenReturn(json); Method getBarCodeByAutoDiscovery = Whitebox.getMethod(AddDeviceV2Service.class, "getBarCodeByAutoDiscovery", String.class); getBarCodeByAutoDiscovery.setAccessible(true); getBarCodeByAutoDiscovery.invoke(addDeviceV2Service, "username"); } @Test public void testGetSmartDataCapabilityByAutoDiscovery() throws InvocationTargetException, IllegalAccessException { FunctionModuleSpecSection param = new FunctionModuleSpecSection(); param.setHttpSmartDataVersion(null); param.setTdcpVersion("tdcp"); param.setSmartAnalysis(1); SmartAnalysisRespDTO smartAnalysisRespDTO = new SmartAnalysisRespDTO(); smartAnalysisRespDTO.setErrorCode(0); String json = JsonUtil.bean2json(smartAnalysisRespDTO); Mockito.when(AuthenticationService.httpPostProcess(any(), any())).thenReturn(json); Method getSmartDataCapabilityByAutoDiscovery = Whitebox.getMethod(AddDeviceV2Service.class, "getSmartDataCapabilityByAutoDiscovery", String.class, FunctionModuleSpecSection.class); getSmartDataCapabilityByAutoDiscovery.setAccessible(true); getSmartDataCapabilityByAutoDiscovery.invoke(addDeviceV2Service, "username", param); } private static @NotNull DiscoveredDeviceDTO getDiscoveredDeviceDTO() { DiscoveredDeviceDTO discoveredDeviceDTO = new DiscoveredDeviceDTO(); discoveredDeviceDTO.setDeviceModel("model"); discoveredDeviceDTO.setSysFactoryDefine(FactoryDefine.NOT_ON_FACTORY_STATUS); discoveredDeviceDTO.setDevId("devId"); discoveredDeviceDTO.setSupportDeviceFirmwareVersion("1.0"); discoveredDeviceDTO.setIpAddressString("ipAddressString"); discoveredDeviceDTO.setDeviceName("deviceName"); discoveredDeviceDTO.setDiscoverDeviceType(DiscoverDeviceType.TP_DISCOVERY_DEVICE_TYPE_ACCESSCONTROLDEVICE); discoveredDeviceDTO.setMacAddress("macAddress"); return discoveredDeviceDTO; } private static @NotNull AddDiscoveredDeviceDTO getAddDiscoveredDeviceDTO() { AddDiscoveredDeviceDTO request = new AddDiscoveredDeviceDTO(); request.setUserId("userId"); request.setUuid("uuid"); request.setVmsId("vmsId"); request.setDiscoverType(DiscoverType.AUTO); request.setSelectAll(true); request.setSearchValue("model"); request.setUsername("username"); request.setPassword("password"); return request; } @Test public void testWebSocketResultCacheOperations() { AddDiscoveredDeviceDTO request = new AddDiscoveredDeviceDTO(); request.setUserId("userId"); request.setUuid("uuid"); request.setVmsId("vmsId"); request.setDiscoverType(DiscoverType.AUTO); request.setSelectAll(true); request.setSearchValue("model"); request.setUsername("username"); request.setPassword("password"); // 测试delete操作 addDeviceV2Service.handleAddDiscoveredDevices(request); verify(webSocketResultCacheService).delete(eq(request.getVmsId()), eq(request.getUuid())); // 测试setValue操作 verify(webSocketResultCacheService).setValue( eq(request.getVmsId()), eq(request.getUuid()), any(WebSocketResultDTO.class) ); } @Test public void testAddIpcDeviceByDeviceDiscovery() throws InvocationTargetException, IllegalAccessException { DiscoveredDeviceDTO discoveredDeviceDTO = getDiscoveredDeviceDTO(); AddDiscoveredDeviceDTO request = getAddDiscoveredDeviceDTO(); FunctionModuleSpecSection functionModuleSpecSection = new FunctionModuleSpecSection(); functionModuleSpecSection.setMaxChannels("1"); Mockito.when(passThroughService.getModuleSpecInfo(any())).thenReturn(Optional.of(functionModuleSpecSection)); Mockito.when(ipcInfoService.getCapabilityByIpcModuleSpec(any())).thenReturn(1L); Mockito.when(ipcInfoService.getCapabilityObjectByIpcModuleSpec(any())).thenReturn(new CapabilityObjectDO()); Mockito.when(deviceSmartDataCapabilityService.getSmartDataCapabilityByIpcModuleSpec(any(), any(), any())) .thenReturn(new SmartDataCapability()); DeviceDO deviceDO = new DeviceDO(); deviceDO.setId(1L); deviceDO.setOnlineStatus(OnlineStatus.OFFLINE); Mockito.when(deviceInternalRepository.findByDevId(any())).thenReturn(Optional.of(deviceDO)); Mockito.when(deviceConnectorService.getOnlineStatus(any())).thenReturn(1); Mockito.when(OnlineStatus.findByCode(anyInt())).thenReturn(OnlineStatus.ONLINE); Mockito.when(smartDetectionCapabilityService.getSmartDetectionCapability(any())) .thenReturn(new SmartDetectionCapability()); Mockito.when(ipcRepository.save(any())).thenReturn(1L); Method addIpcDeviceByDeviceDiscovery = Whitebox.getMethod(AddDeviceV2Service.class, "addIpcDeviceByDeviceDiscovery", DiscoveredDeviceDTO.class, DiscoverType.class, AddDiscoveredDeviceDTO.class , String.class, Long.class, String.class, Long.class); addIpcDeviceByDeviceDiscovery.setAccessible(true); addIpcDeviceByDeviceDiscovery.invoke(addDeviceV2Service, discoveredDeviceDTO, DiscoverType.REMOTE, request, "username", 1L, "password", 1L); } }
最新发布
11-01
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值