KKSU
2024-11-15 2147ca2f66dd5ff83db5080988f4832bd10ac213
feat(unisoftiot): 新增设备和产品相关功能

- 添加 Account、Device、Product 等实体类
- 实现设备控制、产品列表等 API 接口- 集成 UniSoftAccount 配置和请求处理
- 新增 JSON 解析和 OkHttp 工具类
- 更新数据库配置和 Mapper 接口
24 files added
2 files deleted
3 files modified
2374 ■■■■■ changed files
pom.xml 6 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/FebsShiroApplication.java 2 ●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/unisoftiot/api/entity/Account.java 11 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/unisoftiot/api/entity/Device.java 9 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/unisoftiot/api/entity/Product.java 39 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/unisoftiot/api/mapper/AccountMapper.java 7 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/unisoftiot/api/mapper/DeviceMapper.java 7 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/unisoftiot/api/mapper/ProductMapper.java 7 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/unisoftiot/api/service/DeviceService.java 10 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/unisoftiot/api/service/InitAccountService.java 12 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/unisoftiot/api/service/ProductService.java 15 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/unisoftiot/api/service/impl/DeviceServiceImpl.java 53 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/unisoftiot/api/service/impl/InitAccountServiceImpl.java 28 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/unisoftiot/api/service/impl/ProductServiceImpl.java 41 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/unisoftiot/api/vo/ApiProductVo.java 35 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/unisoftiot/config/RequestBuilder.java 72 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/unisoftiot/config/RequestHandler.java 105 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/unisoftiot/config/ResponseHandler.java 104 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/unisoftiot/config/UniSoftAccount.java 34 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/unisoftiot/enums/DeviceRequestUrlEnum.java 59 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/unisoftiot/enums/HttpMethod.java 9 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/unisoftiot/enums/ProductRequestUrlEnum.java 27 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/unisoftiot/enums/ResponseCodeEnum.java 92 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/unisoftiot/utils/JSONParser.java 39 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/unisoftiot/utils/OkHttpUtils.java 330 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/unisoftiot/utils/UrlUtils.java 160 ●●●●● patch | view | raw | blame | history
src/main/resources/application-prod.yml 14 ●●●● patch | view | raw | blame | history
src/test/java/cc/mrbird/febs/PayTest.java 71 ●●●●● patch | view | raw | blame | history
src/test/java/cc/mrbird/febs/ProfitTest.java 976 ●●●●● patch | view | raw | blame | history
pom.xml
@@ -28,6 +28,12 @@
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.json</groupId>
            <artifactId>json</artifactId>
            <version>20230618</version>
        </dependency>
        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-apache-httpclient</artifactId>
src/main/java/cc/mrbird/febs/FebsShiroApplication.java
@@ -20,7 +20,7 @@
@EnableScheduling
@SpringBootApplication
@EnableTransactionManagement
@MapperScan("cc.mrbird.febs.*.mapper")
@MapperScan({"cc.mrbird.febs.*.mapper", "cc.mrbird.febs.unisoftiot.*.mapper"})
public class FebsShiroApplication {
    public static void main(String[] args) {
src/main/java/cc/mrbird/febs/unisoftiot/api/entity/Account.java
New file
@@ -0,0 +1,11 @@
package cc.mrbird.febs.unisoftiot.api.entity;
import lombok.Data;
@Data
public class Account {
    private String appId;
    private String appSecret;
}
src/main/java/cc/mrbird/febs/unisoftiot/api/entity/Device.java
New file
@@ -0,0 +1,9 @@
package cc.mrbird.febs.unisoftiot.api.entity;
import lombok.Data;
@Data
public class Device {
    private String deviceId;
}
src/main/java/cc/mrbird/febs/unisoftiot/api/entity/Product.java
New file
@@ -0,0 +1,39 @@
package cc.mrbird.febs.unisoftiot.api.entity;
import lombok.Data;
/**
 * Product类代表一个产品实体,用于在平台中存储和管理产品相关信息
 * 它包含了产品的基本属性,如产品ID、型号、名称、图标、生产商、设备数量和备注
 */
@Data
public class Product {
    /**
     * 产品ID,平台唯一,固定不变
     */
    private int id;
    /**
     * 产品型号
     */
    private String model;
    /**
     * 产品名称
     */
    private String title;
    /**
     * 产品图标
     */
    private String icon;
    /**
     * 生产商,Unisoft或自定义
     */
    private String producer;
    /**
     * 工作台(控制台)下此产品类型的设备数量
     */
    private Integer device;
    /**
     * 备注
     */
    private String remark;
}
src/main/java/cc/mrbird/febs/unisoftiot/api/mapper/AccountMapper.java
New file
@@ -0,0 +1,7 @@
package cc.mrbird.febs.unisoftiot.api.mapper;
import cc.mrbird.febs.unisoftiot.api.entity.Account;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface AccountMapper extends BaseMapper<Account> {
}
src/main/java/cc/mrbird/febs/unisoftiot/api/mapper/DeviceMapper.java
New file
@@ -0,0 +1,7 @@
package cc.mrbird.febs.unisoftiot.api.mapper;
import cc.mrbird.febs.unisoftiot.api.entity.Device;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface DeviceMapper extends BaseMapper<Device> {
}
src/main/java/cc/mrbird/febs/unisoftiot/api/mapper/ProductMapper.java
New file
@@ -0,0 +1,7 @@
package cc.mrbird.febs.unisoftiot.api.mapper;
import cc.mrbird.febs.unisoftiot.api.entity.Product;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface ProductMapper extends BaseMapper<Product> {
}
src/main/java/cc/mrbird/febs/unisoftiot/api/service/DeviceService.java
New file
@@ -0,0 +1,10 @@
package cc.mrbird.febs.unisoftiot.api.service;
import cc.mrbird.febs.unisoftiot.api.entity.Device;
import com.baomidou.mybatisplus.extension.service.IService;
public interface DeviceService extends IService<Device> {
    //控制设备,向设备下发指令
    String controlDevice(Device device);
}
src/main/java/cc/mrbird/febs/unisoftiot/api/service/InitAccountService.java
New file
@@ -0,0 +1,12 @@
package cc.mrbird.febs.unisoftiot.api.service;
import cc.mrbird.febs.unisoftiot.api.entity.Account;
import cc.mrbird.febs.unisoftiot.config.UniSoftAccount;
import com.baomidou.mybatisplus.extension.service.IService;
public interface InitAccountService  extends IService<Account> {
    UniSoftAccount initAccount(Account account);
    UniSoftAccount initAccountByAppInfo(String appId, String appSecret);
}
src/main/java/cc/mrbird/febs/unisoftiot/api/service/ProductService.java
New file
@@ -0,0 +1,15 @@
package cc.mrbird.febs.unisoftiot.api.service;
import cc.mrbird.febs.unisoftiot.api.entity.Product;
import cc.mrbird.febs.unisoftiot.api.vo.ApiProductVo;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
public interface ProductService  extends IService<Product> {
    /**
     * 获取产品列表
     */
    List<ApiProductVo> getProductList();
}
src/main/java/cc/mrbird/febs/unisoftiot/api/service/impl/DeviceServiceImpl.java
New file
@@ -0,0 +1,53 @@
package cc.mrbird.febs.unisoftiot.api.service.impl;
import cc.mrbird.febs.unisoftiot.api.entity.Account;
import cc.mrbird.febs.unisoftiot.api.entity.Device;
import cc.mrbird.febs.unisoftiot.api.mapper.DeviceMapper;
import cc.mrbird.febs.unisoftiot.api.service.DeviceService;
import cc.mrbird.febs.unisoftiot.api.service.InitAccountService;
import cc.mrbird.febs.unisoftiot.config.UniSoftAccount;
import cc.mrbird.febs.unisoftiot.enums.DeviceRequestUrlEnum;
import cc.mrbird.febs.unisoftiot.utils.UrlUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.LinkedHashMap;
@Slf4j
@Service
@RequiredArgsConstructor
public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> implements DeviceService {
    private final InitAccountService initAccountService;
    /**
     * 控制设备的方法
     * 该方法用于向设备发送控制指令,例如开关设备等
     *
     * @param device 设备对象,包含设备的相关信息
     * @return 返回控制设备的结果,以字符串形式表示
     */
    @Override
    public String controlDevice(Device device) {
        // 初始化账户服务,为设备控制做准备
        UniSoftAccount uniSoftAccount = initAccountService.initAccountByAppInfo(UrlUtils.APP_ID,UrlUtils.APP_SECRET);
        // 创建参数集合,用于设备控制
        LinkedHashMap<String, Object> parameters = new LinkedHashMap<>();
        // 添加控制设备的参数,这里的例子是开启设备的某个功能
        parameters.put("device", "1438");
        parameters.put("power3", "1");
        log.info("parameters",parameters.toString());
        // 发送签名请求,控制设备
        // 这里使用了签名请求来确保设备控制指令的安全性
        return uniSoftAccount.requestHandler.sendSignedRequest(
                UrlUtils.BASE_URL,
                DeviceRequestUrlEnum.DEVICE_CONTROL.getRequestUrl(),
                parameters,
                DeviceRequestUrlEnum.DEVICE_CONTROL.getRequestMethod());
    }
}
src/main/java/cc/mrbird/febs/unisoftiot/api/service/impl/InitAccountServiceImpl.java
New file
@@ -0,0 +1,28 @@
package cc.mrbird.febs.unisoftiot.api.service.impl;
import cc.mrbird.febs.unisoftiot.api.service.InitAccountService;
import cc.mrbird.febs.unisoftiot.api.entity.Account;
import cc.mrbird.febs.unisoftiot.api.mapper.AccountMapper;
import cc.mrbird.febs.unisoftiot.config.UniSoftAccount;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
public class InitAccountServiceImpl extends ServiceImpl<AccountMapper, Account> implements InitAccountService {
    @Override
    public UniSoftAccount initAccount(Account account) {
        return new UniSoftAccount(account.getAppId(),account.getAppSecret());
    }
    @Override
    public UniSoftAccount initAccountByAppInfo(String appId, String appSecret) {
        Account account = new Account();
        account.setAppId(appId);
        account.setAppSecret(appSecret);
        return this.initAccount(account);
    }
}
src/main/java/cc/mrbird/febs/unisoftiot/api/service/impl/ProductServiceImpl.java
New file
@@ -0,0 +1,41 @@
package cc.mrbird.febs.unisoftiot.api.service.impl;
import cc.mrbird.febs.unisoftiot.api.entity.Product;
import cc.mrbird.febs.unisoftiot.api.mapper.ProductMapper;
import cc.mrbird.febs.unisoftiot.api.service.InitAccountService;
import cc.mrbird.febs.unisoftiot.api.service.ProductService;
import cc.mrbird.febs.unisoftiot.api.vo.ApiProductVo;
import cc.mrbird.febs.unisoftiot.config.UniSoftAccount;
import cc.mrbird.febs.unisoftiot.enums.ProductRequestUrlEnum;
import cc.mrbird.febs.unisoftiot.utils.UrlUtils;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.LinkedHashMap;
import java.util.List;
@Slf4j
@Service
@RequiredArgsConstructor
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService {
    private final InitAccountService initAccountService;
    @Override
    public List<ApiProductVo> getProductList() {
        UniSoftAccount uniSoftAccount = initAccountService.initAccountByAppInfo(UrlUtils.APP_ID, UrlUtils.APP_SECRET);
        // 创建参数集合
        LinkedHashMap<String, Object> parameters = new LinkedHashMap<>();
        parameters.put("q", 1);
        String bodyStr = uniSoftAccount.requestHandler.sendSignedRequest(
                UrlUtils.BASE_URL, ProductRequestUrlEnum.PRODUCT_LIST.getRequestUrl(),
                parameters, ProductRequestUrlEnum.PRODUCT_LIST.getRequestMethod());
        JSONObject jsonObject = JSONUtil.parseObj(bodyStr);
        return jsonObject.getJSONArray("data").toList(ApiProductVo.class);
    }
}
src/main/java/cc/mrbird/febs/unisoftiot/api/vo/ApiProductVo.java
New file
@@ -0,0 +1,35 @@
package cc.mrbird.febs.unisoftiot.api.vo;
import lombok.Data;
@Data
public class ApiProductVo {
    /**
     * 产品ID,平台唯一,固定不变
     */
    private int id;
    /**
     * 产品型号
     */
    private String model;
    /**
     * 产品名称
     */
    private String title;
    /**
     * 产品图标
     */
    private String icon;
    /**
     * 生产商,Unisoft或自定义
     */
    private String producer;
    /**
     * 工作台(控制台)下此产品类型的设备数量
     */
    private Integer device;
    /**
     * 备注
     */
    private String remark;
}
src/main/java/cc/mrbird/febs/unisoftiot/config/RequestBuilder.java
New file
@@ -0,0 +1,72 @@
package cc.mrbird.febs.unisoftiot.config;
import cc.mrbird.febs.common.exception.FebsException;
import cc.mrbird.febs.unisoftiot.enums.HttpMethod;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
/**
 * 请求构建器类,用于构建HTTP请求
 */
public final class RequestBuilder {
    // 定义JSON类型的媒体类型常量
    private static final MediaType JSON_TYPE = MediaType.parse("application/json; charset=utf-8");
    // 私有构造方法,防止实例化
    private RequestBuilder() {
    }
    /**
     * 根据提供的参数构建HTTP请求
     *
     * @param fullUrl    完整的请求URL
     * @param body       请求体内容
     * @param httpMethod HTTP方法枚举类型
     * @return 构建好的Request对象
     * @throws FebsException 如果URL无效或HTTP方法不支持,则抛出此异常
     */
    public static Request buildRequest(String fullUrl, String body, HttpMethod httpMethod) {
        // 校验 URL
        if (fullUrl == null || fullUrl.isEmpty()) {
            throw new FebsException("Invalid URL: URL cannot be null or empty");
        }
        // 创建Request.Builder对象用于配置请求
        Request.Builder builder = new Request.Builder();
        try {
            // 根据HTTP方法构建不同的请求
            final Request request;
            switch (httpMethod) {
                case POST:
                    // 构建POST请求,并设置请求体为JSON类型
                    request = builder
                            .url(fullUrl)
                            .post(RequestBody.create(JSON_TYPE, body))
                            .addHeader("X-APISpace-Token","")
                            .addHeader("Content-Type","")
                            .build();
                    break;
                case GET:
                    // 构建GET请求
                    request = builder
                            .url(fullUrl)
                            .get()
                            .addHeader("Content-Type", "application/x-www-form-urlencoded")
                            .build();
                    break;
                default:
                    // 如果HTTP方法不支持,抛出异常
                    throw new FebsException("Invalid HTTP method: " + httpMethod);
            }
            // 返回构建好的请求对象
            return request;
        } catch (IllegalArgumentException e) {
            // 如果URL格式错误,抛出异常
            throw new FebsException("Invalid URL: " + e.getMessage());
        }
    }
}
src/main/java/cc/mrbird/febs/unisoftiot/config/RequestHandler.java
New file
@@ -0,0 +1,105 @@
package cc.mrbird.febs.unisoftiot.config;
import cc.mrbird.febs.common.exception.FebsException;
import cc.mrbird.febs.unisoftiot.enums.HttpMethod;
import cc.mrbird.febs.unisoftiot.utils.UrlUtils;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Request;
import java.util.LinkedHashMap;
/**
 * 请求处理器类,用于构建和发送API请求
 */
@Slf4j
public class RequestHandler {
    private String appId;
    private String appSecret;
    /**
     * 构造函数,初始化appId和appSecret
     *
     * @param appId     应用ID
     * @param appSecret 应用密钥
     */
    public RequestHandler(String appId,String appSecret){
        this.appId = appId;
        this.appSecret = appSecret;
    }
    /**
     * 发送API请求
     *
     * @param baseUrl       基础URL
     * @param urlPath       URL路径
     * @param parameters    请求参数
     * @param httpMethod    HTTP方法
     * @return 请求结果字符串
     * @throws FebsException 当HTTP方法不支持时抛出异常
     */
    private String sendApiRequest(String baseUrl,
                                  String urlPath,
                                  LinkedHashMap<String, Object> parametersw,
                                  HttpMethod httpMethod) {
        // 创建参数集合,用于设备控制
        LinkedHashMap<String, Object> parameters = new LinkedHashMap<>();
        // 添加控制设备的参数,这里的例子是开启设备的某个功能
        parameters.put("device", "1438");
        parameters.put("power3", "1");
        log.info("parameters",parameters.toString());
        String fullUrl = "";
        Request request;
        // 根据HTTP方法构建请求
        switch (httpMethod) {
            case POST:
                // 对于POST请求,将参数序列化为JSON体
                fullUrl = UrlUtils.getUrl(baseUrl,appId,appSecret,urlPath,null);
                String body = JSON.toJSONString(parameters);
                log.info("parametersJson:", JSONUtil.parseObj(parameters));
                log.info("body:",body);
                request = RequestBuilder.buildRequest(fullUrl, body,httpMethod);
                break;
            case GET:
                // 对于GET请求,将参数直接拼接到URL中
                fullUrl = UrlUtils.getUrl(baseUrl,appId,appSecret,urlPath,parameters);
                request = RequestBuilder.buildRequest(fullUrl, null,httpMethod);
                break;
            default:
                // 不支持的HTTP方法抛出异常
                throw new FebsException("[RequestHandler] HttpMethod 不支持: " + httpMethod);
        }
        // 记录请求信息
        log.info("{} {}", httpMethod, fullUrl);
        // 发送请求并处理响应
        return ResponseHandler.handleResponse(request);
    }
    /**
     * 发送签名请求,确保appId和appSecret不为空
     *
     * @param baseUrl       基础URL
     * @param urlPath       URL路径
     * @param parameters    请求参数
     * @param httpMethod    HTTP方法
     * @return 请求结果字符串
     * @throws FebsException 当appId或appSecret为空时抛出异常
     */
    public String sendSignedRequest(String baseUrl,
                                    String urlPath,
                                    LinkedHashMap<String, Object> parameters,
                                    HttpMethod httpMethod) {
        // 校验appId和appSecret
        if (null == appId || appId.isEmpty() || null == appSecret || appSecret.isEmpty()) {
            throw new FebsException("[RequestHandler] appId/appSecret 不能为空!");
        }
        // 调用sendApiRequest方法发送请求
        return sendApiRequest(baseUrl, urlPath, parameters, httpMethod);
    }
}
src/main/java/cc/mrbird/febs/unisoftiot/config/ResponseHandler.java
New file
@@ -0,0 +1,104 @@
package cc.mrbird.febs.unisoftiot.config;
import cc.mrbird.febs.common.exception.FebsException;
import cc.mrbird.febs.unisoftiot.enums.ResponseCodeEnum;
import cc.mrbird.febs.unisoftiot.utils.OkHttpUtils;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.json.JSONException;
import java.io.IOException;
/**
 * 响应处理器类,用于处理OKHTTP请求的响应
 */
public final class ResponseHandler {
    // 错误信息前缀
    private static final String ERROR_PREFIX = "[ResponseHandler] OKHTTP Error: ";
    // 私有构造方法,防止实例化
    private ResponseHandler() {
    }
    /**
     * 处理HTTP响应
     * @param request 请求对象
     * @return 响应体字符串
     * @throws FebsException 当响应处理失败时抛出
     */
    public static String handleResponse(Request request) {
        try (
                // 执行HTTP请求并获取响应
                Response response = OkHttpUtils.builder().okHttpClient.newCall(request).execute()
        ) {
            // 获取响应体内容
            String responseBodyStr = getResponseBodyAsString(response.body());
            if (ResponseCodeEnum.isFail(response.code())) {
                throw new FebsException(ERROR_PREFIX + responseBodyStr);
            }
            // 如果响应内容为空,则抛出异常
            if(StrUtil.isEmpty(responseBodyStr)){
                throw new FebsException(ERROR_PREFIX + "请求返回参数为空");
            }
            // 解析响应内容为JSON对象
            JSONObject jsonObject = new JSONObject(responseBodyStr);
            // 获取响应码
            int code = jsonObject.getInt("code");
            // 如果响应码表示失败,则处理错误响应
            if(ResponseCodeEnum.isFail(code)){
                throw handleErrorResponse(code, responseBodyStr);
            }
            // 获取响应消息
            String msg = jsonObject.getStr("msg");
            // 如果响应消息不正确,则抛出异常
            if(!ResponseCodeEnum.SUCCESS.getMsg().equals(msg)){
                throw new FebsException(ERROR_PREFIX + "请求返回参数msg不正确");
            }
            // 返回响应内容
            return responseBodyStr;
        } catch (IOException | IllegalStateException | JSONException e) {
            // 捕获异常并抛出自定义异常
            throw new FebsException(ERROR_PREFIX + e.getMessage());
        }
    }
    /**
     * 处理错误响应
     * @param code 响应码
     * @param responseBodyStr 响应体字符串
     * @return 自定义异常
     * @throws JSONException 当JSON解析失败时抛出
     */
    private static FebsException handleErrorResponse(int code, String responseBodyStr) {
        try {
            // 获取错误描述和消息
            String desc = ResponseCodeEnum.getDesc(code);
            String msg = ResponseCodeEnum.getMsg(code);
            // 构造并返回自定义异常
            return new FebsException(ERROR_PREFIX + "{" + msg + "},{" + desc + "}");
        } catch (JSONException e) {
            // 如果JSON解析失败,构造并返回自定义异常
            throw new FebsException(ERROR_PREFIX + "{" + responseBodyStr + "}");
        }
    }
    /**
     * 获取响应体内容作为字符串
     * @param body 响应体
     * @return 响应体字符串
     * @throws IOException 当读取响应体失败时抛出
     */
    private static String getResponseBodyAsString(ResponseBody body) throws IOException {
        if (body != null) {
            // 如果响应体不为空,读取并返回内容
            return body.string();
        } else {
            // 如果响应体为空,返回空字符串
            return "";
        }
    }
}
src/main/java/cc/mrbird/febs/unisoftiot/config/UniSoftAccount.java
New file
@@ -0,0 +1,34 @@
package cc.mrbird.febs.unisoftiot.config;
import lombok.Data;
/**
 * 类说明:统一软账号配置类
 * 字段说明:
 *  - appId: 应用ID,用于标识一个应用
 *  - appSecret: 应用密钥,用于验证应用的身份
 *  - requestHandler: 处理HTTP请求的处理器,用于封装HTTP请求的处理逻辑
 */
@Data
public class UniSoftAccount {
    private String appId;
    private String appSecret;
    // 处理HTTP请求的处理器
    public RequestHandler requestHandler;
    /**
     * 构造方法说明:初始化UniSoftAccount实例
     * 参数说明:
     *  - appId: 应用ID,用于标识一个应用
     *  - appSecret: 应用密钥,用于验证应用的身份
     *  - requestHandler: 处理HTTP请求的处理器,用于封装HTTP请求的处理逻辑
     */
    public UniSoftAccount(String appId, String appSecret){
        this.appId = appId;
        this.appSecret = appSecret;
        this.requestHandler = new RequestHandler(appId, appSecret);
    }
}
src/main/java/cc/mrbird/febs/unisoftiot/enums/DeviceRequestUrlEnum.java
New file
@@ -0,0 +1,59 @@
package cc.mrbird.febs.unisoftiot.enums;
import lombok.Getter;
/**
 * 设备请求URL枚举类
 * 用于定义与设备相关的API请求的URL和方法
 */
@Getter
public enum DeviceRequestUrlEnum {
    /**
     * 设备控制请求配置
     * 使用POST方法发送设备控制指令
     */
    DEVICE_CONTROL(HttpMethod.POST,"/device/control"),
    /**
     * 设备分组请求配置
     * 使用POST方法对设备进行分组操作
     */
    DEVICE_GROUP(HttpMethod.POST,"/device/group"),
    /**
     * 设备标签请求配置
     * 使用POST方法添加或修改设备标签
     */
    DEVICE_TAG(HttpMethod.POST,"/device/tag"),
    /**
     * 设备列表请求配置
     * 使用POST方法获取设备列表
     */
    DEVICE_LIST(HttpMethod.POST,"/device/list"),
    /**
     * 设备信息请求配置
     * 使用POST方法获取特定设备的信息
     */
    DEVICE_INFO(HttpMethod.POST,"/device/info");
    // 请求方法,如POST、GET等
    private HttpMethod requestMethod;
    // 请求的URL路径
    private String requestUrl;
    /**
     * 构造函数,初始化设备请求的URL和方法
     *
     * @param requestMethod HTTP请求方法,例如POST
     * @param requestUrl HTTP请求的URL路径
     */
    DeviceRequestUrlEnum(HttpMethod requestMethod, String requestUrl) {
        this.requestMethod = requestMethod;
        this.requestUrl = requestUrl;
    }
}
src/main/java/cc/mrbird/febs/unisoftiot/enums/HttpMethod.java
New file
@@ -0,0 +1,9 @@
package cc.mrbird.febs.unisoftiot.enums;
public enum HttpMethod {
    POST,
    GET,
    PUT,
    DELETE,
    INVALID
}
src/main/java/cc/mrbird/febs/unisoftiot/enums/ProductRequestUrlEnum.java
New file
@@ -0,0 +1,27 @@
package cc.mrbird.febs.unisoftiot.enums;
import lombok.Getter;
@Getter
public enum ProductRequestUrlEnum {
    PRODUCT_LIST(HttpMethod.POST,"/product/list");
    // 请求方法,如POST、GET等
    private HttpMethod requestMethod;
    // 请求的URL路径
    private String requestUrl;
    /**
     * 构造函数,初始化设备请求的URL和方法
     *
     * @param requestMethod HTTP请求方法,例如POST
     * @param requestUrl HTTP请求的URL路径
     */
    ProductRequestUrlEnum(HttpMethod requestMethod, String requestUrl) {
        this.requestMethod = requestMethod;
        this.requestUrl = requestUrl;
    }
}
src/main/java/cc/mrbird/febs/unisoftiot/enums/ResponseCodeEnum.java
New file
@@ -0,0 +1,92 @@
package cc.mrbird.febs.unisoftiot.enums;
import lombok.Getter;
/**
 * 响应码枚举类,用于定义API响应的各种状态码及其含义
 */
@Getter
public enum ResponseCodeEnum {
    // 定义各种错误响应码和成功响应码
    FAIL_5009(5009, "too many request", "单个设备访问最高限制1次/秒,请勿超过此限制"),
    FAIL_5008(5008, "ip:*.*.*.* is not in white list", "开启了IP检查,但接口访问IP不在白名单内,请在控制台的开发设置中,将IP加入白名单"),
    FAIL_5007(5007, "bad debug key", "指定了debug参数,但未开启调试模式,或者是debug参数不正确或已过期"),
    FAIL_5006(5006, "bad sign", "签名错误,正确签名为 md5(md5(开发者密码) + (url中的ts参数))"),
    FAIL_5005(5005, "please set app secret", "未设置开发者密码,请在控制台的开发设置中进行设置"),
    FAIL_5004(5004, "miss param sign", "请在请求接口中指定sign(签名),这是一个计算值"),
    FAIL_5003(5003, "bad ts", "时间戳错误,ts为请求发生时的时间,是一个动态的值,必须为中国时间"),
    FAIL_5002(5002, "miss param ts", "请在请求接口中指定ts(时间戳),取值为请求时的时间戳,10位数字"),
    FAIL_5001(5001, "miss app id", "请求地址为 https://iot-api.unisoft.cn/{APPID}/,缺少{APPID},请在控制台查看您的appid"),
    SUCCESS(200, "ok", "");
    // 响应码
    private int code;
    // 简短消息
    private String msg;
    // 描述信息
    private String desc;
    /**
     * 构造函数,初始化响应码、消息和描述
     *
     * @param code 响应码
     * @param msg  简短消息
     * @param desc 描述信息
     */
    ResponseCodeEnum(int code, String msg, String desc) {
        this.code = code;
        this.msg = msg;
        this.desc = desc;
    }
    /**
     * 根据响应代码获取描述信息
     * 该方法用于将系统内部的响应代码转换为人类可读的描述信息
     * 主要通过遍历枚举类ResponseCodeEnum来匹配传入的代码,并返回对应的描述
     * 如果没有找到匹配的代码,则返回空字符串
     *
     * @param code 响应代码,用于查找对应的描述信息
     * @return 描述信息如果找到匹配的代码,否则返回空字符串
     */
    public static String getDesc(int code) {
        for (ResponseCodeEnum item : ResponseCodeEnum.values()) {
            if (item.getCode() == code) {
                return item.getDesc();
            }
        }
        return "";
    }
    /**
     * 根据响应代码获取消息信息
     * 该方法用于将系统内部的响应代码转换为消息信息
     * 主要通过遍历枚举类ResponseCodeEnum来匹配传入的代码,并返回对应的消息
     * 如果没有找到匹配的代码,则返回空字符串
     *
     * @param code 响应代码,用于查找对应的消息信息
     * @return 消息信息如果找到匹配的代码,否则返回空字符串
     */
    public static String getMsg(int code) {
        for (ResponseCodeEnum item : ResponseCodeEnum.values()) {
            if (item.getCode() == code) {
                return item.getMsg();
            }
        }
        return "";
    }
    /**
     * 验证响应码是200
     */
    public static boolean isSuccess(int code) {
        return SUCCESS.getCode() == code;
    }
    /**
     * 验证响应码不是200
     */
    public static boolean isFail(int code) {
        return !isSuccess(code);
    }
}
src/main/java/cc/mrbird/febs/unisoftiot/utils/JSONParser.java
New file
@@ -0,0 +1,39 @@
package cc.mrbird.febs.unisoftiot.utils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
public final class JSONParser {
    private JSONParser() {
    }
    public static String getJSONStringValue(String json, String key) {
        try {
            JSONObject obj = new JSONObject(json);
            return obj.getString(key);
        } catch (JSONException e) {
            throw new JSONException(String.format("[JSONParser] Failed to get \"%s\"  from JSON object", key));
        }
    }
    public static int getJSONIntValue(String json, String key) {
        try {
            JSONObject obj = new JSONObject(json);
            return obj.getInt(key);
        } catch (JSONException e) {
            throw new JSONException(String.format("[JSONParser] Failed to get \"%s\" from JSON object", key));
        }
    }
    public static String getJSONArray(ArrayList<?> symbols, String key) {
        try {
            JSONArray arr = new JSONArray(symbols);
            return arr.toString();
        } catch (JSONException e) {
            throw new JSONException(String.format("[JSONParser] Failed to convert \"%s\" to JSON array", key));
        }
    }
}
src/main/java/cc/mrbird/febs/unisoftiot/utils/OkHttpUtils.java
New file
@@ -0,0 +1,330 @@
package cc.mrbird.febs.unisoftiot.utils;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.json.JSONObject;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.net.URLEncoder;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
 * OkHttp请求工具封装
 * 参考:https://blog.csdn.net/DwZ735660836/article/details/119976068
 */
@Slf4j
public class OkHttpUtils {
    public static volatile OkHttpClient okHttpClient = null;
    private static volatile Semaphore semaphore = null;
    private Map<String, String> headerMap;
    private Map<String, String> paramMap;
    private String url;
    private Request.Builder request;
    // 开发环境用的 ShadowsocksR-dotnet4.0 免费版本 正式环境得使用外网服务器
    // 安易代理  http://127.0.0.1:10809/ http://127.0.0.1:10808/
    /**
     * 初始化okHttpClient,并且允许https访问
     */
    private OkHttpUtils() {
        if (okHttpClient == null) {
            synchronized (OkHttpUtils.class) {
                if (okHttpClient == null) {
                    TrustManager[] trustManagers = buildTrustManagers();
                    okHttpClient = new OkHttpClient.Builder()
                            .connectTimeout(30, TimeUnit.SECONDS)
                            .writeTimeout(20, TimeUnit.SECONDS)
                            .readTimeout(60, TimeUnit.SECONDS)
                            .sslSocketFactory(createSSLSocketFactory(trustManagers), (X509TrustManager) trustManagers[0])
                            //.hostnameVerifier((hostName, session) -> true)
                            //配置自定义连接池参数
                            .connectionPool(new ConnectionPool(5, 60, TimeUnit.SECONDS))
                            .retryOnConnectionFailure(true)
                            .build();
                    addHeader("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36");
                    addHeader("Connection", "close");
                    addHeader("Accept-Encoding", "identity");
                }
            }
        }
    }
    /**
     * 用于异步请求时,控制访问线程数,返回结果
     *
     * @return
     */
    private static Semaphore getSemaphoreInstance() {
        //只能1个线程同时访问
        synchronized (OkHttpUtils.class) {
            if (semaphore == null) {
                semaphore = new Semaphore(0);
            }
        }
        return semaphore;
    }
    /**
     * 创建OkHttpUtils
     *
     * @return
     */
    public static OkHttpUtils builder() {
        return new OkHttpUtils();
    }
    /**
     * 添加url
     *
     * @param url
     * @return
     */
    public OkHttpUtils url(String url) {
        this.url = url;
        return this;
    }
    /**
     * 添加参数
     *
     * @param key   参数名
     * @param value 参数值
     * @return
     */
    public OkHttpUtils addParam(String key, String value) {
        if (paramMap == null) {
            paramMap = new LinkedHashMap<>(16);
        }
        paramMap.put(key, value);
        return this;
    }
    /**
     * 添加请求头
     *
     * @param key   参数名
     * @param value 参数值
     * @return
     */
    public OkHttpUtils addHeader(String key, String value) {
        if (headerMap == null) {
            headerMap = new LinkedHashMap<>(16);
        }
        headerMap.put(key, value);
        return this;
    }
    /**
     * 初始化get方法
     *
     * @return
     */
    public OkHttpUtils get() {
        request = new Request.Builder().get();
        StringBuilder urlBuilder = new StringBuilder(url);
        if (paramMap != null) {
            urlBuilder.append("?");
            try {
                for (Map.Entry<String, String> entry : paramMap.entrySet()) {
                    urlBuilder.append(URLEncoder.encode(entry.getKey(), "utf-8")).
                            append("=").
                            append(URLEncoder.encode(entry.getValue(), "utf-8")).
                            append("&");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            urlBuilder.deleteCharAt(urlBuilder.length() - 1);
        }
        request.url(urlBuilder.toString());
        return this;
    }
    /**
     * 初始化post方法
     *
     * @param isJsonPost true等于json的方式提交数据,类似postman里post方法的raw
     *                   false等于普通的表单提交
     * @return
     */
    public OkHttpUtils post(boolean isJsonPost) {
        RequestBody requestBody;
        if (isJsonPost) {
            String json = "";
            if (paramMap != null) {
                json = JSONObject.valueToString(paramMap);
            }
            requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json);
        } else {
            FormBody.Builder formBody = new FormBody.Builder();
            if (paramMap != null) {
                paramMap.forEach(formBody::add);
            }
            requestBody = formBody.build();
        }
        request = new Request.Builder().post(requestBody).url(url);
        return this;
    }
    /**
     * 同步请求
     *
     * @return
     */
    public Request.Builder sync() {
        return setHeader(request);
    }
    /**
     * 同步请求
     *
     * @return
     */
    public String syncStr() {
        setHeader(request);
        try {
            Response response = okHttpClient.newCall(request.build()).execute();
            assert response.body() != null;
            return response.body().string();
        } catch (IOException e) {
            e.printStackTrace();
            return "请求失败:" + e.getMessage();
        }
    }
    /**
     * 异步请求,有返回值
     */
    public String async() {
        StringBuilder buffer = new StringBuilder("");
        setHeader(request);
        okHttpClient.newCall(request.build()).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                buffer.append("请求出错:").append(e.getMessage());
            }
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                assert response.body() != null;
                buffer.append(response.body().string());
                getSemaphoreInstance().release();
            }
        });
        try {
            getSemaphoreInstance().acquire();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return buffer.toString();
    }
    /**
     * 异步请求,带有接口回调
     *
     * @param callBack
     */
    public void async(ICallBack callBack) {
        setHeader(request);
        okHttpClient.newCall(request.build()).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                callBack.onFailure(call, e.getMessage());
            }
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                assert response.body() != null;
                callBack.onSuccessful(call, response.body().string());
            }
        });
    }
    /**
     * 为request添加请求头
     *
     * @param request
     */
    private Request.Builder setHeader(Request.Builder request) {
        if (headerMap != null) {
            try {
                for (Map.Entry<String, String> entry : headerMap.entrySet()) {
                    request.addHeader(entry.getKey(), entry.getValue());
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return request;
    }
    /**
     * 生成安全套接字工厂,用于https请求的证书跳过
     *
     * @return
     */
    private static SSLSocketFactory createSSLSocketFactory(TrustManager[] trustAllCerts) {
        SSLSocketFactory ssfFactory = null;
        try {
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, trustAllCerts, new SecureRandom());
            ssfFactory = sc.getSocketFactory();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ssfFactory;
    }
    private static TrustManager[] buildTrustManagers() {
        return new TrustManager[]{
                new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(X509Certificate[] chain, String authType) {
                    }
                    @Override
                    public void checkServerTrusted(X509Certificate[] chain, String authType) {
                    }
                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return new X509Certificate[]{};
                    }
                }
        };
    }
    /**
     * 自定义一个接口回调
     */
    public interface ICallBack {
        void onSuccessful(Call call, String data);
        void onFailure(Call call, String errorMsg);
    }
    public static void main(String[] args) {
        String url = "https://api2.binance.com/api/v3/ticker/24hr?symbol=BNBUSDT&type=MINI";
        String result = OkHttpUtils.builder()
                .url(url)
                .addHeader("Content-Type", "application/x-www-form-urlencoded")
                .get()
                .syncStr();
        System.out.println(result);
    }
}
src/main/java/cc/mrbird/febs/unisoftiot/utils/UrlUtils.java
New file
@@ -0,0 +1,160 @@
package cc.mrbird.febs.unisoftiot.utils;
import cc.mrbird.febs.unisoftiot.enums.DeviceRequestUrlEnum;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.crypto.SecureUtil;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.Map;
/**
 * URL工具类,用于构造请求物联网控制台接口的URL
 */
public class UrlUtils {
    /**
     * http(s)://iot-api.unisoft.cn/{AppID}/{接口列表中的path}/?{其他参数}&sign={sign}&ts={ts}
     *
     * sign    签名    所有请求物联网控制台接口,均需在url中携带此参数sign={sign}
     * 取值方法:md5(md5(开发者密码) + 上面的ts参数),32位字符串
     * ts    时间戳    所有请求物联网控制台接口,均需在url中携带此参数ts={timestap}
     * 取值方法:请求时间(timezone,东八区),10位数字
     */
    public static void main(String[] args) {
        System.out.println(getUrl(BASE_URL,APP_ID,APP_SECRET, DeviceRequestUrlEnum.DEVICE_CONTROL.getRequestUrl(), null));
        LinkedHashMap<String, Object> parameters = new LinkedHashMap<>();
        parameters.put("group", "123456789");
        parameters.put("product", "test");
        System.out.println(getUrl(BASE_URL,APP_ID,APP_SECRET,"device/info",parameters));
    }
    // todo 优化到后台配置中
    public static final String BASE_URL = " http://iot-api.unisoft.cn/rtyVWcgmlr";
    public static final String APP_ID = "rtyVWcgmlr";
    public static final String APP_SECRET = "rtyVWcgmlr";
    /**
     * 构造请求物联网控制台接口的URL
     *
     * @param baseUrl       基础URL地址
     * @param appId         应用ID,用于标识应用
     * @param appSecret     应用密钥,用于生成签名
     * @param requestPath   请求路径,标识具体的接口
     * @param parameters    请求参数,包含除sign和ts之外的其他参数
     * @return              返回构造完成的URL字符串
     *
     * 该方法首先会构建基础URL,然后添加appId和请求路径,接着添加其他参数,
     * 最后通过appSecret和时间戳生成签名(sign),以及添加时间戳(ts)到URL中。
     */
    public static String getUrl(String baseUrl,
                                String appId,
                                String appSecret,
                                String requestPath,
                                LinkedHashMap<String, Object> parameters){
        StringBuilder fullUrl = new StringBuilder(baseUrl);
//        fullUrl.append("/"+appId);
        fullUrl.append(requestPath);
        fullUrl.append("?");
        //判断parameters参数是否为空,不为空则拼接在fullUrl
        parametersSetToUrl(fullUrl,parameters);
        getSignAndTs(appSecret,fullUrl,parameters);
        return fullUrl.toString();
    }
    /**
     * 将查询参数附加到URL路径上
     * 此方法接受一个URL路径和一个查询参数的映射,然后将这些参数附加到URL路径上
     * 如果没有查询参数,URL路径保持不变
     *
     * @param urlPath URL路径的StringBuilder对象,用于构建最终的URL
     * @param parameters 查询参数的LinkedHashMap,键为参数名,值为参数值
     * @return 返回包含查询参数的URL路径的StringBuilder对象
     */
    public static StringBuilder parametersSetToUrl(StringBuilder urlPath, LinkedHashMap<String, Object> parameters) {
        // 检查参数是否为空,如果为空则直接返回URL路径
        if (parameters == null || parameters.isEmpty()) {
            return urlPath;
        }
        joinQueryParameters(urlPath, parameters);
        // 返回包含查询参数的URL路径
        return urlPath;
    }
    /**
     * 将查询参数拼接到URL路径后
     *
     * @param urlPath   URL路径
     * @param parameters 查询参数
     * @return 拼接后的URL路径
     */
    public static StringBuilder joinQueryParameters(StringBuilder urlPath, LinkedHashMap<String, Object> parameters) {
        if (parameters == null || parameters.isEmpty()) {
            return urlPath;
        }
        boolean isFirst = true;
        for (Map.Entry<String, Object> mapElement : parameters.entrySet()) {
            Object value = mapElement.getValue();
            // 根据是否是第一个参数来决定是否添加&
            if (isFirst) {
                isFirst = false;
            } else {
                urlPath.append('&');
            }
            // 拼接参数到URL路径
            urlPath.append(mapElement.getKey())
                    .append('=')
                    .append(urlEncode(value.toString()));
        }
        return urlPath;
    }
    /**
     * 对URL参数进行编码
     *
     * @param s 参数字符串
     * @return 编码后的参数字符串
     */
    public static String urlEncode(String s) {
        try {
            return URLEncoder.encode(s, StandardCharsets.UTF_8.name());
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(StandardCharsets.UTF_8.name() + " is unsupported", e);
        }
    }
    /**
     * 生成签名和时间戳参数
     * 该方法用于给定的URL路径添加签名(sign)和时间戳(ts)参数,以确保请求的安全性和有效性
     * 签名是通过将appSecret进行MD5加密,然后与当前时间戳拼接后再进行MD5加密生成
     * 这种方法确保了每个请求都有一个唯一的、基于时间的签名,从而防止了重放攻击
     *
     * @param appSecret 应用密钥,用于生成签名
     * @param urlPath StringBuilder对象,用于拼接URL路径和查询参数
     * @param parameters 请求参数,用于判断是否需要添加额外的连接符
     * @return 返回包含签名和时间戳参数的URL路径字符串
     */
    public static String getSignAndTs(String appSecret,StringBuilder urlPath, LinkedHashMap<String, Object> parameters) {
        //使用MD5加密appSecret
        String md5AppSecret = SecureUtil.md5(appSecret);
        //获取当前时间戳
        String timestamp = String.valueOf(DateUtil.currentSeconds());
        //生成sign参数,通过md5加密(appSecret的md5值 + 时间戳)
        String md5Sign = SecureUtil.md5(md5AppSecret + timestamp);
        //返回包含sign和ts参数的字符串
        if (CollUtil.isNotEmpty(parameters)) {
            urlPath.append("&");
        }
        urlPath.append("sign=");
        urlPath.append(md5Sign);
        urlPath.append("&ts=");
        urlPath.append(timestamp);
        return urlPath.toString();
    }
}
src/main/resources/application-prod.yml
@@ -15,10 +15,10 @@
      datasource:
        # 数据源-1,名称为 base
        base:
          username: blnka
          password: blnka!@#123
          username: db_e2
          password: db_e20806123!@#
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://127.0.0.1:3306/db_blnka?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2b8
          url: jdbc:mysql://127.0.0.1:3306/db_e2?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2b8
  redis:
    # Redis数据库索引(默认为 0)
@@ -26,9 +26,9 @@
    # Redis服务器地址
    host: 127.0.0.1
    # Redis服务器连接端口
    port: 6279
    port: 6379
    # Redis 密码
    password: blnka!@#123
    password: lianghua1!qaz2@WSX
    lettuce:
      pool:
        # 连接池中的最小空闲连接
@@ -44,8 +44,8 @@
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: blnka
    password: blnka123
    username: e20210816
    password: e20210816
    publisher-confirm-type: correlated
pay:
src/test/java/cc/mrbird/febs/PayTest.java
File was deleted
src/test/java/cc/mrbird/febs/ProfitTest.java
File was deleted