diff --git a/mkl-iot/src/main/java/com/ruoyi/iot/api/TowerCraneApiController.java b/mkl-iot/src/main/java/com/ruoyi/iot/api/TowerCraneApiController.java index 9868191..a9c1e94 100644 --- a/mkl-iot/src/main/java/com/ruoyi/iot/api/TowerCraneApiController.java +++ b/mkl-iot/src/main/java/com/ruoyi/iot/api/TowerCraneApiController.java @@ -1,18 +1,20 @@ package com.ruoyi.iot.api; import cn.hutool.http.HttpUtil; +import com.alibaba.fastjson.JSON; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.iot.domain.IotDeviceInfo; +import com.ruoyi.iot.domain.IotPower; import com.ruoyi.iot.domain.TowerReqVo; import com.ruoyi.iot.enums.TowerTypeEnums; +import com.ruoyi.iot.service.IIotDeviceInfoService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; -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 org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.List; @@ -39,6 +41,10 @@ public class TowerCraneApiController { { cacheMap = new HashMap<>(); } + + @Autowired + private IIotDeviceInfoService iIotDeviceInfoService; + /** * 塔吊监测++ * 限流规则[60秒内最多请求10次,限流策略IP] @@ -48,6 +54,7 @@ public class TowerCraneApiController { */ @PostMapping("/v1/push") public AjaxResult pushData(@Validated @RequestBody TowerReqVo req) { + log.info("/towerCrane/v1/push...{}",JSON.toJSONString(req)); if(req.getType() == TowerTypeEnums.BASE.getCode()){ this.pushConfigData(req); }else if(req.getType() == TowerTypeEnums.RUN.getCode()){ @@ -64,6 +71,30 @@ public class TowerCraneApiController { return AjaxResult.success(); } + /** + * 塔吊监测++ + * 限流规则[60秒内最多请求10次,限流策略IP] + * @author JiangYuQi + * @date 2024-01-13 + */ + @GetMapping("/v99/pushIotPower") + public AjaxResult pushIotPower() { + IotDeviceInfo iotDeviceInfo = new IotDeviceInfo(); + iotDeviceInfo.setDeviceId("A3gVjYga"); + iotDeviceInfo.setDeviceName("配电箱"); + iotDeviceInfo.setState(1); + iotDeviceInfo.setProjectId(229L); + iotDeviceInfo.setPoints(1L); + iotDeviceInfo.setTypeName("配电箱"); + iotDeviceInfo.setHzTenantId("2RrXaxJ8"); + iotDeviceInfo.setHzProjectId("8NgaaNgY"); + iotDeviceInfo.setFactoryName("萨达"); + iotDeviceInfo.setCompanyName("中铁一局建安公司"); + iotDeviceInfo.setProjectName("西建中国西电集团智慧产业园"); + iIotDeviceInfoService.insertIotDeviceInfo(iotDeviceInfo); + return AjaxResult.success(); + } + /** * 塔机上报基本信息 * @param req diff --git a/mkl-iot/src/main/java/com/ruoyi/iot/task/GainHzDataTask.java b/mkl-iot/src/main/java/com/ruoyi/iot/task/GainHzDataTask.java index 36b28be..63bbc0b 100644 --- a/mkl-iot/src/main/java/com/ruoyi/iot/task/GainHzDataTask.java +++ b/mkl-iot/src/main/java/com/ruoyi/iot/task/GainHzDataTask.java @@ -30,7 +30,6 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import java.beans.Transient; import java.math.BigDecimal; import java.text.SimpleDateFormat; import java.util.*; @@ -85,88 +84,89 @@ public class GainHzDataTask { IotDeviceInfo iotDeviceInfo = new IotDeviceInfo(); iotDeviceInfo.setDeviceName("配电箱"); - iotDeviceInfo.setFactoryName("华筑"); List iotDeviceInfos = iIotDeviceInfoService.selectIotDeviceInfoList(iotDeviceInfo); iotDeviceInfos.forEach(item -> { - if(item.getHzTenantId() != null && item.getHzProjectId() != null) { - JSONObject body = new JSONObject(); - body.put("tenantId", item.getHzTenantId()); - body.put("projectId", item.getHzProjectId()); - body.put("startTime", startTime); - body.put("endTime", endTime); - String res_str = HttpRequest.post(HzApiConf.IOT_POWER_HOST + HzApiConf.DISTRIBUTION_BOX_PAGE_REAL_DATA) - .header("appId", HzApiConf.APP_ID).header("appSecret", HzApiConf.APP_SECRET) - .body(body.toString()) - .execute().body(); - JSONObject res = JSONObject.parseObject(res_str); + if(Objects.isNull(item.getFactoryName()) || !Objects.equals("萨达",item.getFactoryName())){ + if(item.getHzTenantId() != null && item.getHzProjectId() != null) { + JSONObject body = new JSONObject(); + body.put("tenantId", item.getHzTenantId()); + body.put("projectId", item.getHzProjectId()); + body.put("startTime", startTime); + body.put("endTime", endTime); + String res_str = HttpRequest.post(HzApiConf.IOT_POWER_HOST + HzApiConf.DISTRIBUTION_BOX_PAGE_REAL_DATA) + .header("appId", HzApiConf.APP_ID).header("appSecret", HzApiConf.APP_SECRET) + .body(body.toString()) + .execute().body(); + JSONObject res = JSONObject.parseObject(res_str); - //2. 解析数据 - System.out.println(res_str); - JSONArray content = res.getJSONObject("data").getJSONArray("content"); - System.out.println(content); - List iotWarningInfos = new ArrayList<>(16); - List iotPowers = new ArrayList<>(16); - for (int i = 0; i < content.size(); i++) { + //2. 解析数据 + System.out.println(res_str); + JSONArray content = res.getJSONObject("data").getJSONArray("content"); + System.out.println(content); + List iotWarningInfos = new ArrayList<>(16); + List iotPowers = new ArrayList<>(16); + for (int i = 0; i < content.size(); i++) { - //3.数据转换 - JSONObject tmp = content.getJSONObject(i); - IotPower iotPower = new IotPower(); - iotPower.setUid(tmp.getString("deviceSn")); - iotPower.setTime(tmp.getDate("createTime")); - iotPower.setPower(BigDecimal.valueOf(tmp.getFloat("totalPower"))); - iotPower.setLeak(BigDecimal.valueOf(tmp.getFloat("leftElectric"))); - iotPower.setC1(BigDecimal.valueOf(tmp.getFloat("currentElectric1"))); - iotPower.setC2(BigDecimal.valueOf(tmp.getFloat("currentElectric2"))); - iotPower.setC3(BigDecimal.valueOf(tmp.getFloat("currentElectric3"))); - iotPower.setV1(BigDecimal.valueOf(tmp.getFloat("currentVoltage1"))); - iotPower.setV2(BigDecimal.valueOf(tmp.getFloat("currentVoltage2"))); - iotPower.setV3(BigDecimal.valueOf(tmp.getFloat("currentVoltage3"))); - iotPower.setT1(BigDecimal.valueOf(tmp.getFloat("monitorTem1"))); - iotPower.setT2(BigDecimal.valueOf(tmp.getFloat("monitorTem2"))); - iotPower.setT3(BigDecimal.valueOf(tmp.getFloat("monitorTem3"))); - iotPower.setT4(BigDecimal.valueOf(tmp.getFloat("monitorTem4"))); - if (t % 2 == 0 && !warrning[t]) { - warrning = new Boolean[]{false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}; - warrning[t] = true; - if (iotPower.getLeak().floatValue() > HzApiConf.DISTRIBUTION_BOX_WARNING_LEAK) { - IotWarningInfo iotWarningInfo = new IotWarningInfo(); - iotWarningInfo.setDeviceId(iotPower.getUid()); - iotWarningInfo.setType("漏电预警"); - iotWarningInfo.setContent(String.format("设备存在漏电,当前剩余电流:%s", iotPower.getLeak())); - iotWarningInfo.setCreateTime(new Date()); - iotWarningInfos.add(iotWarningInfo); + //3.数据转换 + JSONObject tmp = content.getJSONObject(i); + IotPower iotPower = new IotPower(); + iotPower.setUid(tmp.getString("deviceSn")); + iotPower.setTime(tmp.getDate("createTime")); + iotPower.setPower(BigDecimal.valueOf(tmp.getFloat("totalPower"))); + iotPower.setLeak(BigDecimal.valueOf(tmp.getFloat("leftElectric"))); + iotPower.setC1(BigDecimal.valueOf(tmp.getFloat("currentElectric1"))); + iotPower.setC2(BigDecimal.valueOf(tmp.getFloat("currentElectric2"))); + iotPower.setC3(BigDecimal.valueOf(tmp.getFloat("currentElectric3"))); + iotPower.setV1(BigDecimal.valueOf(tmp.getFloat("currentVoltage1"))); + iotPower.setV2(BigDecimal.valueOf(tmp.getFloat("currentVoltage2"))); + iotPower.setV3(BigDecimal.valueOf(tmp.getFloat("currentVoltage3"))); + iotPower.setT1(BigDecimal.valueOf(tmp.getFloat("monitorTem1"))); + iotPower.setT2(BigDecimal.valueOf(tmp.getFloat("monitorTem2"))); + iotPower.setT3(BigDecimal.valueOf(tmp.getFloat("monitorTem3"))); + iotPower.setT4(BigDecimal.valueOf(tmp.getFloat("monitorTem4"))); + if (t % 2 == 0 && !warrning[t]) { + warrning = new Boolean[]{false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}; + warrning[t] = true; + if (iotPower.getLeak().floatValue() > HzApiConf.DISTRIBUTION_BOX_WARNING_LEAK) { + IotWarningInfo iotWarningInfo = new IotWarningInfo(); + iotWarningInfo.setDeviceId(iotPower.getUid()); + iotWarningInfo.setType("漏电预警"); + iotWarningInfo.setContent(String.format("设备存在漏电,当前剩余电流:%s", iotPower.getLeak())); + iotWarningInfo.setCreateTime(new Date()); + iotWarningInfos.add(iotWarningInfo); + } + + if (iotPower.getT1().floatValue() > HzApiConf.DISTRIBUTION_BOX_WARNING_TEMP + || iotPower.getT2().floatValue() > HzApiConf.DISTRIBUTION_BOX_WARNING_TEMP + || iotPower.getT3().floatValue() > HzApiConf.DISTRIBUTION_BOX_WARNING_TEMP + || iotPower.getT4().floatValue() > HzApiConf.DISTRIBUTION_BOX_WARNING_TEMP) { + IotWarningInfo iotWarningInfo = new IotWarningInfo(); + iotWarningInfo.setDeviceId(iotPower.getUid()); + iotWarningInfo.setType("温度异常"); + iotWarningInfo.setContent(String.format("设备当前温度:%s,%s,%s,%s,有温度高于70度", iotPower.getT1(), iotPower.getT2(), iotPower.getT3(), iotPower.getT4())); + iotWarningInfo.setCreateTime(new Date()); + iotWarningInfos.add(iotWarningInfo); + } } - if (iotPower.getT1().floatValue() > HzApiConf.DISTRIBUTION_BOX_WARNING_TEMP - || iotPower.getT2().floatValue() > HzApiConf.DISTRIBUTION_BOX_WARNING_TEMP - || iotPower.getT3().floatValue() > HzApiConf.DISTRIBUTION_BOX_WARNING_TEMP - || iotPower.getT4().floatValue() > HzApiConf.DISTRIBUTION_BOX_WARNING_TEMP) { - IotWarningInfo iotWarningInfo = new IotWarningInfo(); - iotWarningInfo.setDeviceId(iotPower.getUid()); - iotWarningInfo.setType("温度异常"); - iotWarningInfo.setContent(String.format("设备当前温度:%s,%s,%s,%s,有温度高于70度", iotPower.getT1(), iotPower.getT2(), iotPower.getT3(), iotPower.getT4())); - iotWarningInfo.setCreateTime(new Date()); - iotWarningInfos.add(iotWarningInfo); + //4. 数据入库 + iotPowers.add(iotPower); + + Request request = new Request(); + request.setUri("https://aqzg.makalu.cc/api/iot/power/save"); + request.addBody(JSONObject.parseObject(JSONObject.toJSONString(iotPower), HashMap.class)); + try { + HttpClient.json(request); + } catch (Exception e) { + throw new RuntimeException(e); } } - - //4. 数据入库 - iotPowers.add(iotPower); - - Request request = new Request(); - request.setUri("https://aqzg.makalu.cc/api/iot/power/save"); - request.addBody(JSONObject.parseObject(JSONObject.toJSONString(iotPower), HashMap.class)); - try { - HttpClient.json(request); - } catch (Exception e) { - throw new RuntimeException(e); + if (iotPowers.size() > 0) { + iotPowerService.batchInsertPower(iotPowers); + } + if (iotWarningInfos.size() > 0) { + iotWarningInfoService.batchInsertWarning(iotWarningInfos); } - } - if (iotPowers.size() > 0) { - iotPowerService.batchInsertPower(iotPowers); - } - if (iotWarningInfos.size() > 0) { - iotWarningInfoService.batchInsertWarning(iotWarningInfos); } } }); diff --git a/mkl-iot/src/main/resources/mapper/iot/IotDeviceInfoMapper.xml b/mkl-iot/src/main/resources/mapper/iot/IotDeviceInfoMapper.xml index 99eaa0f..c17a4a5 100644 --- a/mkl-iot/src/main/resources/mapper/iot/IotDeviceInfoMapper.xml +++ b/mkl-iot/src/main/resources/mapper/iot/IotDeviceInfoMapper.xml @@ -52,6 +52,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" type_name, hz_tenant_id, hz_project_id, + factory_name, + company_name, + project_name, #{deviceId}, @@ -62,6 +65,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #{typeName}, #{hzTenantId}, #{hzProjectId}, + #{factoryName}, + #{companyName}, + #{projectName}, diff --git a/mkl-screen/src/main/resources/templates/weixin/screen_v1/index.html b/mkl-screen/src/main/resources/templates/weixin/screen_v1/index.html index 7b81e51..bc068a1 100644 --- a/mkl-screen/src/main/resources/templates/weixin/screen_v1/index.html +++ b/mkl-screen/src/main/resources/templates/weixin/screen_v1/index.html @@ -289,7 +289,7 @@ - + diff --git a/mkl-screen/src/main/resources/templates/weixin/screen_v1/labourPersonnelPositioning.html b/mkl-screen/src/main/resources/templates/weixin/screen_v1/labourPersonnelPositioning.html index d8a37c1..d315fad 100644 --- a/mkl-screen/src/main/resources/templates/weixin/screen_v1/labourPersonnelPositioning.html +++ b/mkl-screen/src/main/resources/templates/weixin/screen_v1/labourPersonnelPositioning.html @@ -158,7 +158,7 @@ - + diff --git a/mkl-screen/src/main/resources/templates/weixin/screen_v1/machineryPro.html b/mkl-screen/src/main/resources/templates/weixin/screen_v1/machineryPro.html index 2cbac7c..a3bd372 100644 --- a/mkl-screen/src/main/resources/templates/weixin/screen_v1/machineryPro.html +++ b/mkl-screen/src/main/resources/templates/weixin/screen_v1/machineryPro.html @@ -555,7 +555,7 @@ - + diff --git a/mkl-screen/src/main/resources/templates/weixin/screen_v1/machineryPro_copy.html b/mkl-screen/src/main/resources/templates/weixin/screen_v1/machineryPro_copy.html index 3e76f5e..6d3df36 100644 --- a/mkl-screen/src/main/resources/templates/weixin/screen_v1/machineryPro_copy.html +++ b/mkl-screen/src/main/resources/templates/weixin/screen_v1/machineryPro_copy.html @@ -390,7 +390,7 @@ - + diff --git a/mkl-screen/src/main/resources/templates/weixin/screen_v1/machineryProject.html b/mkl-screen/src/main/resources/templates/weixin/screen_v1/machineryProject.html index 3a45603..e0b9813 100644 --- a/mkl-screen/src/main/resources/templates/weixin/screen_v1/machineryProject.html +++ b/mkl-screen/src/main/resources/templates/weixin/screen_v1/machineryProject.html @@ -328,7 +328,7 @@ - + diff --git a/mkl-screen/src/main/resources/templates/weixin/screen_v1/projectOverviewProject.html b/mkl-screen/src/main/resources/templates/weixin/screen_v1/projectOverviewProject.html index b3e267f..65779d4 100644 --- a/mkl-screen/src/main/resources/templates/weixin/screen_v1/projectOverviewProject.html +++ b/mkl-screen/src/main/resources/templates/weixin/screen_v1/projectOverviewProject.html @@ -823,7 +823,7 @@ - + diff --git a/ruoyi-admin/src/main/resources/application-druid.yml b/ruoyi-admin/src/main/resources/application-druid.yml index 37d8129..80fe8b2 100644 --- a/ruoyi-admin/src/main/resources/application-druid.yml +++ b/ruoyi-admin/src/main/resources/application-druid.yml @@ -6,9 +6,33 @@ spring: druid: # 主库数据源 master: - url: jdbc:mysql://62.234.3.186:3306/mkl_cas2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&serverTimezone=GMT%2B8 + # url: jdbc:mysql://rm-wz91cyo73auob3500o.mysql.rds.aliyuncs.com:3306/mkl_cas_demonstration?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&serverTimezone=GMT%2B8 + # username: weihu + # password: mkl!2021 + url: jdbc:mysql://10.4.9.233:3306/mkl_cas_v1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&serverTimezone=GMT%2B8 username: root - password: Sxyanzhu@cf123 + password: Makalu2024 + # url: jdbc:mysql://rm-wz91cyo73auob3500o.mysql.rds.aliyuncs.com:3306/mkl_cas_test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&serverTimezone=GMT%2B8 + # username: weihu + # password: mkl!2021 + # url: jdbc:mysql://192.168.31.130:3306/mkl_cas_v1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&serverTimezone=GMT%2B8 + # username: root + # password: root + # url: jdbc:mysql://rm-wz91cyo73auob3500o.mysql.rds.aliyuncs.com:3306/mkl_aqsc?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&serverTimezone=GMT%2B8 + # username: weihu + # password: mkl!2021 + # url: jdbc:mysql://rm-wz91cyo73auob3500o.mysql.rds.aliyuncs.com:3306/mkl_cas_v1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&serverTimezone=GMT%2B8 + # username: weihu + # password: mkl!2021 + # url: jdbc:mysql://127.0.0.1:3306/mkl_aqsc?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&serverTimezone=GMT%2B8 + # username: root + # password: root + #中铁三局数字三局 + # url: jdbc:mysql://rm-wz91s59439vhza70ho.mysql.rds.aliyuncs.com/mkl_cas_ztsj?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&serverTimezone=GMT%2B8 + # username: weihu + # password: mkl!2021 + + # 从库数据源 slave: # 从数据源开关/默认关闭 diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index 471213a..3838989 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -11,8 +11,10 @@ ruoyi: # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath) #中铁一局建安公司 profile: /mnt/data/typt/uploadPath + # 安全生产、数字三局 + # profile: /mnt/typt/uploadPath # 本地 -# profile: D://mnt/typt/uploadPath + # profile: D://mnt/typt/uploadPath # 获取ip地址开关 addressEnabled: true @@ -142,17 +144,43 @@ swagger: # cas配置 cas: client-name: CasClientTYPT + # client-name: CasClientTYPTSJ + # client-name: aqscClient server: - url: http://62.234.3.186/cas + # url: https://cas-ty.makalu.cc/cas + # url: https://cas-sj.makalu.cc/cas + url: https://jaszpt-cas.crfeb.com.cn/cas + # url: http://localhost:8080/cas project: - url: http://127.0.0.1:9004 + # url: http://typt-t.makalu.cc + # url: https://typt-t1.makalu.cc + # url: https://szh.makalu.cc + # url: https://aqsc.makalu.cc + # url: http://127.0.0.1:9004 + url: https://jaszpt.crfeb.com.cn + # url:https://sz-test.makalu.cc/ + # url: https://ztsj.makalu.cc/ + # url: https://szsj.makalu.cc/ + user-center: + # url: https://user-center-sj.makalu.cc + url: https://jaszpt-usercenter.crfeb.com.cn +# url: https://user-center.makalu.cc +# url: 127.0.0.1:8099 # helmet文件下载路径路径配置 helmet: -#建安公司数字化集成管控平台 + #建安公司数字化集成管控平台 localFilePath: /mnt/data/aqm/ - downloadFileHost: http://127.0.0.1:9004/typt/ + downloadFileHost: https://jaszpt.crfeb.com.cn/typt/ downloadWritePath: /mnt/data/typt/ templateFilePath: /mnt/data/temporary/ + +#一局三公司安全生产 、数字三局 +# localFilePath: /mnt/aqm/ +# downloadFileHost: https://filedown.makalu.cc/ +# downloadWritePath: /mnt/typt/ +# templateFilePath: /mnt/temporary/ + + #本地 # localFilePath: D:/aqm/ # downloadFileHost: D:/mnt/typt/ diff --git a/ruoyi-admin/src/main/resources/logback.xml b/ruoyi-admin/src/main/resources/logback.xml index a8c526e..d39907d 100644 --- a/ruoyi-admin/src/main/resources/logback.xml +++ b/ruoyi-admin/src/main/resources/logback.xml @@ -1,31 +1,31 @@ - + - + - - - - ${log.pattern} - - - - - - ${log.path}/sys-info.log + + + + ${log.pattern} + + + + + + ${log.path}/sys-info.log - + - ${log.path}/sys-info.%d{yyyy-MM-dd}.log - - 60 - - - ${log.pattern} - - + ${log.path}/sys-info.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + INFO @@ -33,16 +33,16 @@ DENY - - - - ${log.path}/sys-error.log + + + + ${log.path}/sys-error.log ${log.path}/sys-error.%d{yyyy-MM-dd}.log - - 60 + + 60 ${log.pattern} @@ -50,16 +50,16 @@ ERROR - + ACCEPT - + DENY - - + + - ${log.path}/sys-user.log + ${log.path}/sys-user.log ${log.path}/sys-user.%d{yyyy-MM-dd}.log @@ -70,23 +70,23 @@ ${log.pattern} - - - - - - - - - - + + + + + + + + + + - - + +