Bläddra i källkod

水电服务对接

wangzhengliang 3 år sedan
förälder
incheckning
d220299b20

+ 128 - 0
src/main/java/com/chuanghai/ihotel/component/WaterElectricComponent.java

@@ -0,0 +1,128 @@
+package com.chuanghai.ihotel.component;
+
+import com.chuanghai.ihotel.common.exception.BizCodeEnume;
+import com.chuanghai.ihotel.common.exception.RRException;
+import com.chuanghai.ihotel.config.WaterElectricConfig;
+import com.chuanghai.ihotel.constant.RedisKey;
+import com.chuanghai.ihotel.util.WaterElectricDataEncAndDecUtil;
+import com.chuanghai.ihotel.util.WaterElectricSignUtil;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Component;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.util.StringUtils;
+import org.springframework.web.client.RestTemplate;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 水电服务
+ */
+@Slf4j
+@Component
+public class WaterElectricComponent {
+
+    private static final DateTimeFormatter DTF = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
+
+    @Autowired
+    private StringRedisTemplate stringRedisTemplate;
+    @Autowired
+    private WaterElectricConfig waterElectricConfig;
+
+    /**
+     * 获取token
+     * @return
+     */
+    public String getToken() {
+        String token = stringRedisTemplate.opsForValue().get(RedisKey.WATER_AND_ELECTRIC_SERVICE_TOKEN);
+        if (StringUtils.hasText(token)) {
+            return token;
+        } else {
+            HashMap<String, String> map = new HashMap<>();
+            map.put("operatorSecret", waterElectricConfig.getOperatorSecret());
+            try {
+                JsonNode jsonNode = queryData("queryToken", map, null);
+                String accessToken = jsonNode.get("accessToken").asText();
+                long ttl = jsonNode.get("tokenAvailableTime").asLong();
+                stringRedisTemplate.opsForValue().set(RedisKey.WATER_AND_ELECTRIC_SERVICE_TOKEN, accessToken, ttl, TimeUnit.SECONDS);
+
+                return accessToken;
+            } catch (Exception e) {
+                throw new RRException(BizCodeEnume.THIRD_PARTY_SERVICE_CALL_FAILED, "请求水电接口token异常");
+            }
+        }
+    }
+
+    /**
+     * 请求数据
+     *
+     * @param path 请求接口url
+     * @param paramMap 请求参数
+     * @param token 请求token 获取token时该字段可以为空
+     */
+    private JsonNode queryData(String path, Map<String, String> paramMap, String token) throws Exception {
+        ObjectMapper mapper = new ObjectMapper();
+
+        // 加密data部分
+        String encode = WaterElectricDataEncAndDecUtil.dataEncode(mapper.writeValueAsString(paramMap), waterElectricConfig.getDataSecret(), waterElectricConfig.getDataSecretIv());
+        String seq = "0001";
+        String timeStamp = DTF.format(LocalDateTime.now());
+        MultiValueMap<String, String> requestMap = new LinkedMultiValueMap<>();
+        requestMap.add("operatorId", waterElectricConfig.getOperatorId());
+        requestMap.add("data", encode);
+        requestMap.add("timeStamp", timeStamp);
+        requestMap.add("seq", seq);
+
+        // 为防止 + 在收发过程中被变成空格,将加密后的密文中的所有空格替换一下,需要签名的参数: operatorId + 加密后的data + 时间戳 + 自增序列
+        String signSrc = waterElectricConfig.getOperatorId() + encode.replaceAll(" ", "+") + timeStamp + seq;
+        String sig = WaterElectricSignUtil.sign(signSrc, waterElectricConfig.getSignKey());
+        requestMap.add("sig", sig);
+
+        RestTemplate restTemplate = new RestTemplate();
+        HttpHeaders headers = new HttpHeaders();
+        if (StringUtils.hasText(token)) {
+            headers.add("token", token);
+        }
+        HttpEntity<MultiValueMap<String, String>> formEntity = new HttpEntity<>(requestMap, headers);
+        ResponseEntity<String> responseEntity = restTemplate.postForEntity(waterElectricConfig.getServiceHost() + path, formEntity, String.class);
+        String body = responseEntity.getBody();
+
+        JsonNode jsonNode = mapper.readTree(body);
+        if (!"0".equals(jsonNode.get("ret").asText())) {
+            log.error("接口请求失败,【{}】", body);
+        }
+
+        // 验签
+        String dataS = jsonNode.get("data").asText().replaceAll(" ", "+");
+        String resultSigStr = jsonNode.get("operatorId").asText()
+                + dataS
+                + jsonNode.get("msg").asText()
+                + jsonNode.get("ret").asText();
+
+        // 本地签名
+        String localSign = WaterElectricSignUtil.sign(resultSigStr, waterElectricConfig.getSignKey());
+
+        String remoteSign = jsonNode.get("sig").asText();
+        if (!(StringUtils.hasText(remoteSign) && remoteSign.equals(localSign))) {
+            log.error("接口验签失败,本地签名【{}】,远程签名【{}】", localSign, remoteSign);
+        }
+
+        String data = WaterElectricDataEncAndDecUtil.dataDecode(dataS, waterElectricConfig.getDataSecret(), waterElectricConfig.getDataSecretIv());
+        if (!StringUtils.hasText(data)) {
+            log.error("水电服务接口data解密失败");
+        }
+        JsonNode dataJson = mapper.readTree(data);
+        return dataJson;
+    }
+}

+ 38 - 0
src/main/java/com/chuanghai/ihotel/config/WaterElectricConfig.java

@@ -0,0 +1,38 @@
+package com.chuanghai.ihotel.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 水电服务配置
+ */
+@Configuration
+@ConfigurationProperties(prefix = "water-electric")
+@Data
+public class WaterElectricConfig {
+    /**
+     * 运营商标识
+     */
+    private String operatorId;
+    /**
+     * 运营商秘钥
+     */
+    private String operatorSecret;
+    /**
+     * 运营商数据秘钥
+     */
+    private String dataSecret;
+    /**
+     * 数据秘钥初始化向量
+     */
+    private String dataSecretIv;
+    /**
+     * 签名秘钥
+     */
+    private String signKey;
+    /**
+     * 服务地址
+     */
+    private String serviceHost;
+}

+ 1 - 0
src/main/java/com/chuanghai/ihotel/constant/RedisKey.java

@@ -18,4 +18,5 @@ public class RedisKey {
      * 锁定房源
      * 锁定房源
      */
      */
     public static final String LOCK_ROOM_BY_ROOM_TYPE = "lock:room:roomType:%s";
     public static final String LOCK_ROOM_BY_ROOM_TYPE = "lock:room:roomType:%s";
+    public static final String WATER_AND_ELECTRIC_SERVICE_TOKEN = "cache:water-electric-token";
 }
 }

+ 72 - 32
src/main/java/com/chuanghai/ihotel/controller/HotelOrderController.java

@@ -1,6 +1,5 @@
 package com.chuanghai.ihotel.controller;
 package com.chuanghai.ihotel.controller;
 
 
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.chuanghai.ihotel.anno.AdminLoginCheck;
 import com.chuanghai.ihotel.anno.AdminLoginCheck;
 import com.chuanghai.ihotel.anno.ParamCheck;
 import com.chuanghai.ihotel.anno.ParamCheck;
 import com.chuanghai.ihotel.anno.RepeatSubmit;
 import com.chuanghai.ihotel.anno.RepeatSubmit;
@@ -22,6 +21,7 @@ import com.chuanghai.ihotel.vo.ConfirmOrderVO;
 import com.chuanghai.ihotel.vo.LoginUserVO;
 import com.chuanghai.ihotel.vo.LoginUserVO;
 import com.chuanghai.ihotel.vo.OrderSubmitTokenVO;
 import com.chuanghai.ihotel.vo.OrderSubmitTokenVO;
 import com.chuanghai.ihotel.vo.OrderSubmitVO;
 import com.chuanghai.ihotel.vo.OrderSubmitVO;
+import com.chuanghai.ihotel.vo.UserOrderDetailVO;
 import com.chuanghai.ihotel.vo.UserOrderIndexVO;
 import com.chuanghai.ihotel.vo.UserOrderIndexVO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.StringRedisTemplate;
 import org.springframework.data.redis.core.StringRedisTemplate;
@@ -39,7 +39,6 @@ import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalDateTime;
 import java.time.ZoneOffset;
 import java.time.ZoneOffset;
 import java.time.temporal.ChronoUnit;
 import java.time.temporal.ChronoUnit;
-import java.util.Arrays;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeUnit;
 
 
 /**
 /**
@@ -64,7 +63,7 @@ public class HotelOrderController {
      * @return
      * @return
      */
      */
     @UserLoginCheck
     @UserLoginCheck
-    @GetMapping("submit/token")
+    @GetMapping("/user/submit/token")
     public CommonResult<OrderSubmitTokenVO> getOrderToken(@RequestHeader("user_token") String userToken){
     public CommonResult<OrderSubmitTokenVO> getOrderToken(@RequestHeader("user_token") String userToken){
         LoginUserVO loginUserVO = LoginCheckAspect.threadLocal.get();
         LoginUserVO loginUserVO = LoginCheckAspect.threadLocal.get();
         String cardNumber = loginUserVO.getCardNumber();
         String cardNumber = loginUserVO.getCardNumber();
@@ -86,7 +85,7 @@ public class HotelOrderController {
      * @param request 预定参数
      * @param request 预定参数
      * @return
      * @return
      */
      */
-    @GetMapping("confirm/order")
+    @GetMapping("/user/order/confirm")
     @ParamCheck
     @ParamCheck
     public CommonResult<ConfirmOrderVO> confirmOrder(ConfrimOrderRequest request) {
     public CommonResult<ConfirmOrderVO> confirmOrder(ConfrimOrderRequest request) {
         // 时间校验
         // 时间校验
@@ -108,7 +107,7 @@ public class HotelOrderController {
     @UserLoginCheck
     @UserLoginCheck
     @ParamCheck(index = 3)
     @ParamCheck(index = 3)
     @RepeatSubmit(limitType = RepeatSubmit.Type.TOKEN)
     @RepeatSubmit(limitType = RepeatSubmit.Type.TOKEN)
-    @PostMapping("submit/order")
+    @PostMapping("user/order/submit")
     public CommonResult<OrderSubmitVO> submitOrder(@RequestHeader("user_token") String userToken,
     public CommonResult<OrderSubmitVO> submitOrder(@RequestHeader("user_token") String userToken,
                                     @RequestHeader("request_token") String requestToken,
                                     @RequestHeader("request_token") String requestToken,
                                     @RequestBody SubmitOrderRequest request) {
                                     @RequestBody SubmitOrderRequest request) {
@@ -127,7 +126,7 @@ public class HotelOrderController {
      * @return
      * @return
      */
      */
     @UserLoginCheck
     @UserLoginCheck
-    @GetMapping("/user/page")
+    @GetMapping("/user/order/page")
     public CommonResult<PageUtils<UserOrderIndexVO>> userOrderPage(@RequestHeader("user_token") String userToken,
     public CommonResult<PageUtils<UserOrderIndexVO>> userOrderPage(@RequestHeader("user_token") String userToken,
                                                                    PageParam pageParam,
                                                                    PageParam pageParam,
                                                                    OrderQueryRequest request) {
                                                                    OrderQueryRequest request) {
@@ -139,7 +138,7 @@ public class HotelOrderController {
      * 订单列表【管理端】
      * 订单列表【管理端】
      */
      */
     @AdminLoginCheck
     @AdminLoginCheck
-    @GetMapping("/admin/page")
+    @GetMapping("/admin/order/page")
     public CommonResult<PageUtils<HotelOrderEntity>> page(@RequestHeader("admin_token") String adminToken,
     public CommonResult<PageUtils<HotelOrderEntity>> page(@RequestHeader("admin_token") String adminToken,
                                                           PageParam pageParam,
                                                           PageParam pageParam,
                                                           OrderQueryRequest request){
                                                           OrderQueryRequest request){
@@ -147,45 +146,86 @@ public class HotelOrderController {
         return CommonResult.ok().setResult(page);
         return CommonResult.ok().setResult(page);
     }
     }
 
 
+    /**
+     * 用户订单详情
+     * @param userToken 用户token
+     * @param orderId 订单id
+     * @return
+     */
+    @UserLoginCheck
+    @GetMapping("user/order/{orderId}")
+    public CommonResult<UserOrderDetailVO> userOrderDetail(@RequestHeader("user_token") String userToken,
+                                                           @PathVariable("orderId") Long orderId) {
+        UserOrderDetailVO detailVO = hotelOrderService.userOrderDetail(orderId);
+
+        return CommonResult.ok().setResult(detailVO);
+    }
 
 
     /**
     /**
-     * 信息
+     * 获取订单支付参数
+     * @param userToken 用户token
+     * @param orderId 订单id
+     * @return
      */
      */
-    @GetMapping("/info/{id}")
-    public CommonResult<HotelOrderEntity> info(@PathVariable("id") Long id){
-		HotelOrderEntity hotelOrder = hotelOrderService.getById(id);
+    @UserLoginCheck
+    @GetMapping("/user/order/pay/{orderId}")
+    public CommonResult<OrderSubmitVO> getOrderPayParam(@RequestHeader("user_token") String userToken,
+                                                           @PathVariable("orderId") Long orderId) {
+        OrderSubmitVO vo = hotelOrderService.getOrderPayParam(orderId);
+        return CommonResult.ok().setResult(vo);
+    }
 
 
-        return CommonResult.ok().setResult(hotelOrder);
+    /**
+     * 删除订单【用户端】
+     * @param userToken 用户token
+     * @param orderId 订单id
+     * @return
+     */
+    @UserLoginCheck
+    @DeleteMapping("user/order/{orderId}")
+    public CommonResult<String> userDeleteOrder(@RequestHeader("user_token") String userToken,
+                                                @PathVariable("orderId") Long orderId) {
+        hotelOrderService.userDeleteOrder(orderId);
+
+        return CommonResult.ok();
     }
     }
 
 
     /**
     /**
-     * 修改
+     * 取消订单【用户端】
+     * @param userToken 用户token
+     * @param orderId 订单id
+     * @return
      */
      */
-    @PutMapping("/update")
-    public CommonResult<String> update(@RequestBody HotelOrderEntity hotelOrder){
-		boolean flag = hotelOrderService.updateById(hotelOrder);
-
-		if (flag) {
-            return CommonResult.ok();
-        } else {
-		    return CommonResult.fail();
-        }
+    @UserLoginCheck
+    @PutMapping("user/order/cancel/{orderId}")
+    public CommonResult<String> userCancelOrder(@RequestHeader("user_token") String userToken,
+                                                @PathVariable("orderId") Long orderId) {
+        hotelOrderService.userCancelOrder(orderId);
+
+        return CommonResult.ok();
     }
     }
 
 
+    // 办理入住 -> 门锁、水电
+
+
+
+    // 办理续住
+    // 退房 -> 订单结算、水电、门锁
+
+
+
+
     /**
     /**
-     * 删除订单
+     * 信息
      */
      */
-    @DeleteMapping("/delete")
-    public CommonResult<String> delete(@RequestBody Long[] ids){
-        boolean flag = hotelOrderService.removeByIds(Arrays.asList(ids));
-
-        if (flag) {
-            return CommonResult.ok();
-        } else {
-            return CommonResult.fail();
-        }
+    @GetMapping("/info/{id}")
+    public CommonResult<HotelOrderEntity> info(@PathVariable("id") Long id){
+		HotelOrderEntity hotelOrder = hotelOrderService.getById(id);
+
+        return CommonResult.ok().setResult(hotelOrder);
     }
     }
 
 
+
     /**
     /**
      * 时间校验
      * 时间校验
      * @param startTime
      * @param startTime

+ 20 - 0
src/main/java/com/chuanghai/ihotel/controller/TestController.java

@@ -0,0 +1,20 @@
+package com.chuanghai.ihotel.controller;
+
+import com.chuanghai.ihotel.component.WaterElectricComponent;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("test")
+public class TestController {
+
+    @Autowired
+    WaterElectricComponent waterElectricComponent;
+
+    @GetMapping("test1")
+    public void test1() {
+        waterElectricComponent.getToken();
+    }
+}

+ 351 - 0
src/main/java/com/chuanghai/ihotel/util/HttpClientHelper.java

@@ -0,0 +1,351 @@
+package com.chuanghai.ihotel.util;
+
+import jodd.util.StringUtil;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpHost;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.config.Registry;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.config.SocketConfig;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.conn.socket.PlainConnectionSocketFactory;
+import org.apache.http.conn.ssl.NoopHostnameVerifier;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.impl.conn.DefaultProxyRoutePlanner;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.protocol.HTTP;
+import org.apache.http.ssl.SSLContexts;
+import org.apache.http.util.EntityUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.net.ssl.SSLContext;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
+public class HttpClientHelper {
+
+
+    private static final Logger logger = LoggerFactory.getLogger(HttpClientHelper.class);
+    private static PoolingHttpClientConnectionManager poolConnManager = null;
+    private int maxTotalPool = 200;
+    private int maxConPerRoute = 20;
+    private int socketTimeout = 100000;
+    private int connectionRequestTimeout = 90000;
+    private int connectTimeout = 90000;
+
+    // 代理信息
+    public static String proxy_ip;
+    public static int proxy_port;
+
+    private static HttpClientHelper httpClientHelper = null;
+
+    private HttpClientHelper() {
+        init();
+    }
+
+    private static synchronized void syncInit() {
+        if (httpClientHelper == null) {
+            httpClientHelper = new HttpClientHelper();
+        }
+    }
+
+    public static HttpClientHelper getInstance(String proxyIp, Integer proxyPort) {
+        proxy_port = proxyPort;
+        proxy_ip = proxyIp;
+        if (httpClientHelper == null) {
+            syncInit();
+        }
+        return httpClientHelper;
+    }
+
+    public static HttpClientHelper getInstance() {
+        if (httpClientHelper == null) {
+            syncInit();
+        }
+        return httpClientHelper;
+    }
+
+    public PoolingHttpClientConnectionManager getConnManager() {
+        PoolingHttpClientConnectionManager cm = null;
+        try {
+            SSLContext sslcontext = SSLContexts.custom().loadTrustMaterial(null, new TrustSelfSignedStrategy()).build();
+            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, NoopHostnameVerifier.INSTANCE);
+            Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create().register("http",
+                    PlainConnectionSocketFactory.getSocketFactory()).register("https",
+                    sslsf).build();
+            cm = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
+            // 将最大连接数增加到200
+            cm.setMaxTotal(maxTotalPool);
+            // 将每个路由基础的连接增加到20
+            cm.setDefaultMaxPerRoute(maxConPerRoute);
+        } catch (Exception e) {
+            logger.error("InterfacePhpUtilManager init Exception" + e.toString());
+        }
+        return cm;
+    }
+
+    public void init() {
+        try {
+            SSLContext sslcontext = SSLContexts.custom().loadTrustMaterial(null, new TrustSelfSignedStrategy()).build();
+            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, NoopHostnameVerifier.INSTANCE);
+            Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create().register("http",
+                    PlainConnectionSocketFactory.getSocketFactory()).register("https",
+                    sslsf).build();
+            poolConnManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
+            // Increase max total connection to 200
+            poolConnManager.setMaxTotal(maxTotalPool);
+            // Increase default max connection per route to 20
+            poolConnManager.setDefaultMaxPerRoute(maxConPerRoute);
+            SocketConfig socketConfig = SocketConfig.custom().setSoTimeout(socketTimeout).build();
+            poolConnManager.setDefaultSocketConfig(socketConfig);
+        } catch (Exception e) {
+            logger.error("InterfacePhpUtilManager init Exception" + e.toString());
+        }
+    }
+
+    public CloseableHttpClient getConnection() {
+        RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(connectionRequestTimeout).setConnectTimeout(connectTimeout).setSocketTimeout(socketTimeout).build();
+        HttpClientBuilder httpClientBuilder = HttpClients.custom();
+        httpClientBuilder.setConnectionManager(poolConnManager).setDefaultRequestConfig(requestConfig);// set proxy
+        if (!StringUtil.isEmpty(proxy_ip) && proxy_port > 0) {
+            HttpHost proxy = new HttpHost(proxy_ip, proxy_port);
+            DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy);
+            httpClientBuilder.setRoutePlanner(routePlanner);
+        }
+
+        CloseableHttpClient httpClient = httpClientBuilder.build();
+        if (poolConnManager != null && poolConnManager.getTotalStats() != null) {
+            logger.info("now client pool " + poolConnManager.getTotalStats().toString());
+        }
+        return httpClient;
+    }
+
+    /**
+     * 发送 GET 请求(HTTP),不带输入数据
+     */
+    public String doGet(String url) {
+        return doGet(url, new HashMap<String,String>());
+    }
+
+    /**
+     * 发送 GET 请求(HTTP),K-V形式
+     */
+    public String doGet(String url, Map<String, String> params) {
+        String apiUrl = url;
+        StringBuffer param = new StringBuffer();
+        int i = 0;
+        for (String key : params.keySet()) {
+            if (i == 0) {
+                param.append("?");
+            } else {
+                param.append("&");
+            }
+            param.append(key).append("=").append(params.get(key));
+            i++;
+        }
+        apiUrl += param;
+        logger.info(apiUrl);
+        String result = null;
+        CloseableHttpClient httpClient = getConnection();
+        CloseableHttpResponse response = null;
+        HttpGet httpPost = null;
+        try {
+            httpPost = new HttpGet(apiUrl);
+            response = httpClient.execute(httpPost);
+            int status = response.getStatusLine().getStatusCode();
+            logger.info("http request url : " + url + " status : " + status);
+
+            if (status >= 200 && status < 300) {
+                HttpEntity entity = response.getEntity();
+                if (entity != null) {
+                    result = EntityUtils.toString(response.getEntity(), "UTF-8");
+                    logger.info("Request result : " + result);
+                }
+            }
+            EntityUtils.consume(response.getEntity());
+            response.close();
+            return result;
+
+        } catch (IOException e) {
+            logger.error(e.getMessage(), e);
+        } finally {
+            httpPost.releaseConnection();
+            if (response != null) {
+                try {
+                    EntityUtils.consume(response.getEntity());
+                    response.close();
+                } catch (IOException e) {
+                    logger.error(e.getMessage(), e);
+                }
+            }
+        }
+        return result;
+    }
+
+    public String doPost(String url) {
+        return doPost(url, new HashMap<String, String>());
+    }
+
+    /**
+     * 发送 POST json
+     *
+     * @param url 接口URL
+     */
+    public String doPostJson(String url, String jsonstr) {
+        String result = null;
+        HttpPost httpPost = new HttpPost(url);
+        CloseableHttpClient httpClient = getConnection();
+        CloseableHttpResponse response = null;
+        try {
+
+            StringEntity se = new StringEntity(jsonstr, Charset.forName("UTF-8"));
+            se.setContentType("text/json");
+            se.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE, "application/json"));
+            httpPost.setEntity(se);
+            response = httpClient.execute(httpPost);
+            int status = response.getStatusLine().getStatusCode();
+            logger.info("http request url : " + url + " status : " + status);
+            if (status >= 200 && status < 300) {
+                HttpEntity entity = response.getEntity();
+                if (entity != null) {
+                    result = EntityUtils.toString(response.getEntity(), "UTF-8");
+                    logger.info("Request result : " + result);
+                }
+            }
+            EntityUtils.consume(response.getEntity());
+            response.close();
+            return result;
+
+        } catch (IOException e) {
+            logger.error(e.getMessage(), e);
+        } finally {
+            httpPost.releaseConnection();
+            if (response != null) {
+                try {
+                    EntityUtils.consume(response.getEntity());
+                    response.close();
+                } catch (IOException e) {
+                    logger.error(e.getMessage(), e);
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * 发送 POST 请求(HTTP),K-V形式
+     *
+     * @param url    接口URL
+     * @param params 参数map
+     * @return
+     */
+    public String doPost(String url, Map<String, String> params) {
+        String result = null;
+        HttpPost httpPost = new HttpPost(url);
+        CloseableHttpClient httpClient = getConnection();
+        CloseableHttpResponse response = null;
+        try {
+            List<NameValuePair> pairList = new ArrayList<>(params.size());
+            for (Map.Entry<String, String> entry : params.entrySet()) {
+                NameValuePair pair = new BasicNameValuePair(entry.getKey(), entry.getValue());
+                pairList.add(pair);
+            }
+            httpPost.setEntity(new UrlEncodedFormEntity(pairList, Charset.forName("UTF-8")));
+            response = httpClient.execute(httpPost);
+            int status = response.getStatusLine().getStatusCode();
+            logger.info("http request url : " + url + " status : " + status);
+            if (status >= 200 && status < 300) {
+                HttpEntity entity = response.getEntity();
+                if (entity != null) {
+                    result = EntityUtils.toString(response.getEntity(), "UTF-8");
+                    logger.info("Request result : " + result);
+                }
+            }
+            EntityUtils.consume(response.getEntity());
+            response.close();
+            return result;
+
+        } catch (IOException e) {
+            logger.error(e.getMessage(), e);
+        } finally {
+            httpPost.releaseConnection();
+            if (response != null) {
+                try {
+                    EntityUtils.consume(response.getEntity());
+                    response.close();
+                } catch (IOException e) {
+                    logger.error(e.getMessage(), e);
+                }
+            }
+        }
+        return result;
+    }
+    /**
+     * 发送 POST 请求(HTTP),K-V形式
+     *
+     * @param url    接口URL
+     * @param params 参数map
+     * @return
+     */
+    public String doPost(String url, Map<String, String> params, String token) {
+        String result = null;
+        HttpPost httpPost = new HttpPost(url);
+        CloseableHttpClient httpClient = getConnection();
+        CloseableHttpResponse response = null;
+        try {
+            List<NameValuePair> pairList = new ArrayList<>(params.size());
+            for (Map.Entry<String, String> entry : params.entrySet()) {
+                NameValuePair pair = new BasicNameValuePair(entry.getKey(), entry.getValue());
+                pairList.add(pair);
+            }
+            httpPost.setEntity(new UrlEncodedFormEntity(pairList, Charset.forName("UTF-8")));
+            httpPost.addHeader("token", token);
+            response = httpClient.execute(httpPost);
+            int status = response.getStatusLine().getStatusCode();
+            logger.info("http request url : " + url + " status : " + status);
+            if (status >= 200 && status < 300) {
+                HttpEntity entity = response.getEntity();
+                if (entity != null) {
+                    result = EntityUtils.toString(response.getEntity(), "UTF-8");
+                    logger.info("Request result : " + result);
+                }
+            }
+            EntityUtils.consume(response.getEntity());
+            response.close();
+            return result;
+
+        } catch (IOException e) {
+            logger.error(e.getMessage(), e);
+        } finally {
+            httpPost.releaseConnection();
+            if (response != null) {
+                try {
+                    EntityUtils.consume(response.getEntity());
+                    response.close();
+                } catch (IOException e) {
+                    logger.error(e.getMessage(), e);
+                }
+            }
+        }
+        return result;
+    }
+
+}

+ 132 - 0
src/main/java/com/chuanghai/ihotel/util/MyBase64.java

@@ -0,0 +1,132 @@
+package com.chuanghai.ihotel.util;
+
+import java.io.ByteArrayOutputStream;
+
+public class MyBase64 {
+
+    // map 6-bit int to char
+    private static final char[] chars64 = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a',
+                                            'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1',
+                                            '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' };
+
+    // map char to 6-bit int
+    private static final int[]  ints64  = new int[128];
+    static {
+        for (int i = 0; i < 64; i++) {
+            ints64[chars64[i]] = i;
+        }
+    }
+
+    /**
+     * 转换byte数组成Base64 string
+     * 
+     * @param unencoded
+     * @return
+     */
+    public static final String encode(byte[] unencoded) {
+        // Take 24-bits from three octets, translate into four encoded chars.
+        // If necessary, pad with 0 bits on the right at the end
+        // Use = signs as padding at the end to ensure encodedLength % 4 == 0
+        if (unencoded == null || unencoded.length == 0) return null;
+
+        ByteArrayOutputStream out = new ByteArrayOutputStream((int) (unencoded.length * 1.37));
+        int byteCount = 0;
+        int carryOver = 0;
+
+        for (int i = 0; i < unencoded.length; i++) {
+            int bc = (byteCount % 3);
+            byte b = unencoded[i];
+            int lookup = 0;
+
+            // First byte use first six bits, save last two bits
+            if (bc == 0) {
+                lookup = (b >> 2) & 0x3F;
+                carryOver = b & 0x03; // last two bits
+                out.write(chars64[lookup]);
+            } else if (bc == 1) {
+                // Second byte use previous two bits and first four new bits,
+                // save last four bits
+                lookup = ((carryOver << 4) | ((b >> 4) & 0x0F));
+                carryOver = b & 0x0F; // last four bits
+                out.write(chars64[lookup]);
+            } else if (bc == 2) {
+                // Third byte use previous four bits and first two new bits,
+                // then use last six new bits
+                lookup = ((carryOver << 2) | ((b >> 6) & 0x03));
+                out.write(chars64[lookup]);
+
+                lookup = b & 0x3F; // last six bits
+                out.write(chars64[lookup]);
+                carryOver = 0;
+            }
+            byteCount++;
+        }
+
+        if (byteCount % 3 == 1) { // one leftover
+            int lookup = (carryOver << 4) & 0xF0;
+            out.write(chars64[lookup]);
+            out.write('=');
+            out.write('=');
+        } else if (byteCount % 3 == 2) { // two leftovers
+            int lookup = (carryOver << 2) & 0x3C;
+            out.write(chars64[lookup]);
+            out.write('=');
+        }
+        return out.toString();
+    }
+
+    /**
+     * Decode MyBase64 string back to byte array
+     * 
+     * @param encoded
+     * @return
+     */
+    public static final byte[] decode(String encoded) {
+        if (encoded == null || encoded.length() == 0) return null;
+
+        byte[] bytes = encoded.getBytes();
+
+        ByteArrayOutputStream out = new ByteArrayOutputStream((int) (bytes.length * 0.67));
+        int byteCount = 0;
+        int carryOver = 0;
+
+        DECODE_LOOP: for (int i = 0; i < bytes.length; i++) {
+            int ch = bytes[i];
+
+            // Read the next non-whitespace character
+            // if (Character.isWhitespace((char)ch))
+            // continue;
+
+            // The '=' sign is just padding; geffective end of stream
+            if (ch == '=') break DECODE_LOOP;
+
+            // Convert from raw form to 6-bit form
+            int newbits = ints64[ch];
+
+            int bc = (byteCount % 4);
+            if (bc == 0) {
+                // First char save all six bits, go for another
+                carryOver = newbits & 0x3F;
+            } else if (bc == 1) {
+                // second char use 6 previous bits and first 2 new bits
+                int data = ((carryOver << 2) + ((newbits >> 4) & 0x03));
+                out.write(data);
+                carryOver = newbits & 0x0F; // save 4 bits
+            } else if (bc == 2) {
+                // Third char use previous four bits and first four new bits,
+                // save last two bits
+                int data = ((carryOver << 4) + ((newbits >> 2) & 0x0F));
+                out.write(data);
+                carryOver = newbits & 0x03; // save 2 bits
+            } else if (bc == 3) {
+                // Fourth char use previous two bits and all six new bits
+                int data = ((carryOver << 6) + (newbits & 0x3F));
+                out.write(data);
+                carryOver = 0;
+            }
+            byteCount++;
+        }
+
+        return out.toByteArray();
+    }
+}

+ 67 - 0
src/main/java/com/chuanghai/ihotel/util/WaterElectricDataEncAndDecUtil.java

@@ -0,0 +1,67 @@
+package com.chuanghai.ihotel.util;
+
+import org.springframework.util.Assert;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 水电服务data加解密工具
+ */
+public class WaterElectricDataEncAndDecUtil {
+
+    /**
+     * 解密
+     * @param src 源数据
+     * @param secret 秘钥
+     * @param iv 初始化向量
+     * @return String
+     */
+    public static String dataDecode(String src, String secret, String iv){
+        Assert.hasLength(secret, "密钥不能为空");
+        if (secret.length() != 16) {
+            System.out.print("密钥长度不是16位");
+            return null;
+        }
+        try {
+            byte[] raw = secret.getBytes(StandardCharsets.US_ASCII);
+            SecretKeySpec keySpec = new SecretKeySpec(raw, "AES");
+            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+            IvParameterSpec ips  = new IvParameterSpec(iv.getBytes());
+            cipher.init(Cipher.DECRYPT_MODE, keySpec, ips);
+            byte[] encrypted1 = MyBase64.decode(src);
+            try {
+                byte[] original = cipher.doFinal(encrypted1);
+                return new String(original, StandardCharsets.UTF_8);
+            } catch (Exception e) {
+                return null;
+            }
+        } catch (Exception ex) {
+            return null;
+        }
+    }
+
+    /**
+     * 加密
+     * @param src 源数据
+     * @param secret 秘钥
+     * @param iv 初始化向量
+     * @return String
+     */
+    public static String dataEncode(String src, String secret, String iv) throws Exception {
+        Assert.hasLength(secret, "密钥不能为空");
+        if (secret.length() != 16) {
+            System.out.print("密钥长度不是16位");
+            return null;
+        }
+        byte[] raw = secret.getBytes(StandardCharsets.UTF_8);
+        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
+        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");// "算法/模式/补码方式"
+        IvParameterSpec ips = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));// 使用CBC模式,需要一个向量iv,可增加加密算法的强度
+        cipher.init(Cipher.ENCRYPT_MODE, skeySpec, ips);
+        byte[] encrypted = cipher.doFinal(src.getBytes(StandardCharsets.UTF_8));
+        return MyBase64.encode(encrypted);
+    }
+}

+ 131 - 0
src/main/java/com/chuanghai/ihotel/util/WaterElectricSignUtil.java

@@ -0,0 +1,131 @@
+package com.chuanghai.ihotel.util;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * 水电服务参数签名工具
+ */
+public class WaterElectricSignUtil {
+
+    /**
+     * 签名
+     * @param src 源数据
+     * @param signKey 签名秘钥
+     * @return String
+     */
+    public static String sign(String src, String signKey){
+        String result = "";
+        try {
+            byte[] keyByte = signKey.getBytes(StandardCharsets.UTF_8);
+            byte[] dataByte = src.getBytes(StandardCharsets.UTF_8);
+            byte[] hmacMd5Byte = getHmacMd5Bytes(keyByte, dataByte);
+            StringBuilder md5Str = new StringBuilder();
+            for (byte b : hmacMd5Byte) {
+                if (Integer.toHexString(0xFF & b).length() == 1) md5Str.append("0").append(Integer.toHexString(0xFF & b));
+                else md5Str.append(Integer.toHexString(0xFF & b));
+            }
+            result = md5Str.toString().toUpperCase();
+
+        } catch (Exception ignored) {
+        }
+        return result;
+
+    }
+
+    /**
+     * 将待加密数据data,通过密钥key,使用hmac-md5算法进行加密,然后返回加密结果。 参照rfc2104 HMAC算法介绍实现。
+     *
+     * @author sc
+     * @param key 密钥
+     * @param data 待加密数据
+     * @return 加密结果
+     */
+    private static byte[] getHmacMd5Bytes(byte[] key, byte[] data) throws NoSuchAlgorithmException {
+        /*
+         * HmacMd5 calculation formula: H(K XOR opad, H(K XOR ipad, text)) HmacMd5 计算公式:H(K XOR opad, H(K XOR ipad,
+         * text)) H代表hash算法,本类中使用MD5算法,K代表密钥,text代表要加密的数据 ipad为0x36,opad为0x5C。
+         */
+        int length = 64;
+        byte[] ipad = new byte[length];
+        byte[] opad = new byte[length];
+        for (int i = 0; i < 64; i++) {
+            ipad[i] = 0x36;
+            opad[i] = 0x5C;
+        }
+        byte[] actualKey = key; // Actual key.
+        byte[] keyArr = new byte[length]; // Key bytes of 64 bytes length
+        /*
+         * If key's length is longer than 64,then use hash to digest it and use the result as actual key.
+         * 如果密钥长度,大于64字节,就使用哈希算法,计算其摘要,作为真正的密钥。
+         */
+        if (key.length > length) {
+            actualKey = md5(key);
+        }
+        System.arraycopy(actualKey, 0, keyArr, 0, actualKey.length);
+        /*
+         * append zeros to K 如果密钥长度不足64字节,就使用0x00补齐到64字节。
+         */
+        if (actualKey.length < length) {
+            for (int i = actualKey.length; i < keyArr.length; i++)
+                keyArr[i] = 0x00;
+        }
+
+        /*
+         * calc K XOR ipad 使用密钥和ipad进行异或运算。
+         */
+        byte[] firstAppendResult = xor(data, length, ipad, keyArr);
+
+        /*
+         * calc H(K XOR ipad, text) 使用哈希算法计算上面结果的摘要。
+         */
+        byte[] firstHashResult = md5(firstAppendResult);
+
+        /*
+         * calc K XOR opad 使用密钥和opad进行异或运算。
+         */
+        byte[] secondAppendResult = xor(firstHashResult, length, opad, keyArr);
+
+        /*
+         * H(K XOR opad, H(K XOR ipad, text)) 对上面的数据进行哈希运算。
+         */
+        return md5(secondAppendResult);
+    }
+
+    /**
+     * 异或运算
+     * @param data
+     * @param length
+     * @param ipad ipad
+     * @param keyArr 密钥
+     * @return
+     */
+    private static byte[] xor(byte[] data, int length, byte[] ipad, byte[] keyArr) {
+        byte[] kIpadXorResult = new byte[length];
+        for (int i = 0; i < length; i++) {
+            kIpadXorResult[i] = (byte) (keyArr[i] ^ ipad[i]);
+        }
+
+        /*
+         * append "text" to the end of "K XOR ipad" 将待加密数据追加到K XOR ipad计算结果后面。
+         */
+        byte[] firstAppendResult = new byte[kIpadXorResult.length + data.length];
+        System.arraycopy(kIpadXorResult, 0, firstAppendResult, 0, kIpadXorResult.length);
+        System.arraycopy(data, 0, firstAppendResult, keyArr.length, data.length);
+        return firstAppendResult;
+    }
+
+
+    /**
+     * 计算参数的md5信息
+     *
+     * @param str 待处理的字节数组
+     * @return md5摘要信息
+     */
+    private static byte[] md5(byte[] str) throws NoSuchAlgorithmException {
+        MessageDigest md = MessageDigest.getInstance("MD5");
+        md.update(str);
+        return md.digest();
+    }
+}

+ 14 - 0
src/main/resources/application.yml

@@ -70,3 +70,17 @@ jxnxs:
   notifyUrl: http://chuanghai-dev.natappfree.cc/pay/jxnxs/notify
   notifyUrl: http://chuanghai-dev.natappfree.cc/pay/jxnxs/notify
   payUrl: https://q.jxnxs.com/newpay?O=${jxnxs.openId}&out_no=%s&amount=%s&appoint_notify=${jxnxs.notifyUrl}
   payUrl: https://q.jxnxs.com/newpay?O=${jxnxs.openId}&out_no=%s&amount=%s&appoint_notify=${jxnxs.notifyUrl}
 
 
+# 水电服务配置
+water-electric:
+  # 运营商标识
+  operator-id: "000000001"
+  # 运营商秘钥
+  operator-secret: lBej44bqTsoTKstf
+  # 运营商数据秘钥
+  data-secret: 3huqwCCgGpwDs7zA
+  # 数据秘钥初始化向量
+  data-secret-iv: yrpc05EvAYB09KnQ
+  # 签名秘钥
+  sign-key: 8eLaEDi53iHnwOVY
+  # 服务地址
+  service-host: http://172.16.20.37:8181/ems-share-api/