From ad298e6cc2745878d17f2b8e35dfb1b4aaf3d39a Mon Sep 17 00:00:00 2001 From: zk <2762560464@qq.com> Date: Thu, 24 Oct 2024 09:51:16 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=94=AF=E6=8C=81=E4=B8=8EO2=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E8=BF=9B=E8=A1=8C=E6=95=B0=E6=8D=AE=E5=90=8C=E6=AD=A5?= =?UTF-8?q?):?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../appengine/config/WebMvcConfig.java | 38 ++- .../appengine/controller/OpenController.java | 46 ++++ .../controller/SystemController.java | 43 +++- .../appengine/domain/AysnCustomer.java | 41 ++++ .../appengine/domain/CustomerReq.java | 75 ++++++ .../filter/RepeatedlyReadFilter.java | 21 ++ .../appengine/filter/RequestWrapper.java | 85 +++++++ .../appengine/handler/GlobalInterceptor.java | 2 +- .../appengine/handler/SignInterceptor.java | 144 +++++++++++ .../mapper/system/CustomerMapper.java | 30 +++ .../service/system/CustomerService.java | 18 ++ .../system/imp/CustomerServiceImpl.java | 109 +++++++++ .../com/currency/appengine/utils/IpUtils.java | 224 ++++++++++++++++++ .../currency/appengine/utils/SignUtil.java | 66 ++++++ appengine/src/main/resources/application.yml | 23 +- 15 files changed, 944 insertions(+), 21 deletions(-) create mode 100644 appengine/src/main/java/com/currency/appengine/controller/OpenController.java create mode 100644 appengine/src/main/java/com/currency/appengine/domain/AysnCustomer.java create mode 100644 appengine/src/main/java/com/currency/appengine/domain/CustomerReq.java create mode 100644 appengine/src/main/java/com/currency/appengine/filter/RepeatedlyReadFilter.java create mode 100644 appengine/src/main/java/com/currency/appengine/filter/RequestWrapper.java create mode 100644 appengine/src/main/java/com/currency/appengine/handler/SignInterceptor.java create mode 100644 appengine/src/main/java/com/currency/appengine/mapper/system/CustomerMapper.java create mode 100644 appengine/src/main/java/com/currency/appengine/service/system/CustomerService.java create mode 100644 appengine/src/main/java/com/currency/appengine/service/system/imp/CustomerServiceImpl.java create mode 100644 appengine/src/main/java/com/currency/appengine/utils/IpUtils.java create mode 100644 appengine/src/main/java/com/currency/appengine/utils/SignUtil.java diff --git a/appengine/src/main/java/com/currency/appengine/config/WebMvcConfig.java b/appengine/src/main/java/com/currency/appengine/config/WebMvcConfig.java index 331140e..6199626 100644 --- a/appengine/src/main/java/com/currency/appengine/config/WebMvcConfig.java +++ b/appengine/src/main/java/com/currency/appengine/config/WebMvcConfig.java @@ -1,18 +1,26 @@ package com.currency.appengine.config; -import com.currency.appengine.handler.GlobalInterceptor; +import com.currency.appengine.filter.RepeatedlyReadFilter; import com.currency.appengine.handler.CustomMethodArgumentResolver; +import com.currency.appengine.handler.GlobalInterceptor; +import com.currency.appengine.handler.SignInterceptor; import com.currency.appengine.handler.magic.DevInterceptor; +import java.util.List; +import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; import org.springframework.web.method.support.HandlerMethodArgumentResolver; -import org.springframework.web.servlet.config.annotation.*; - -import java.util.List; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration @EnableWebMvc public class WebMvcConfig implements WebMvcConfigurer { + /** * Interceptor verification token * @@ -28,9 +36,15 @@ public class WebMvcConfig implements WebMvcConfigurer { return new DevInterceptor(); } + @Bean + public SignInterceptor signInterceptor() { + return new SignInterceptor(); + } + @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(GlobalInterceptor()).addPathPatterns("/**"); + registry.addInterceptor(signInterceptor()).addPathPatterns("/open/**");//只拦截open前缀的接口 registry.addInterceptor(DevInterceptor()).addPathPatterns("/swagger-ui.html").addPathPatterns("/magic/**"); } @@ -47,8 +61,9 @@ public class WebMvcConfig implements WebMvcConfigurer { // .allowedOrigins("*") .allowedOriginPatterns("*") .allowCredentials(true) - .allowedMethods("GET","HEAD","POST","PUT","DELETE","OPTIONS") - .maxAge(3600); + .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS") + .maxAge(3600) + ; } /* @@ -69,4 +84,15 @@ public class WebMvcConfig implements WebMvcConfigurer { registry.addResourceHandler("doc.html").addResourceLocations( "classpath:knife4j/"); } + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Bean + public FilterRegistrationBean someFilterRegistration() + { + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setFilter(new RepeatedlyReadFilter()); + registration.addUrlPatterns("/*"); + registration.setName("repeatableFilter"); + registration.setOrder(Ordered.LOWEST_PRECEDENCE); + return registration; + } } diff --git a/appengine/src/main/java/com/currency/appengine/controller/OpenController.java b/appengine/src/main/java/com/currency/appengine/controller/OpenController.java new file mode 100644 index 0000000..758ea28 --- /dev/null +++ b/appengine/src/main/java/com/currency/appengine/controller/OpenController.java @@ -0,0 +1,46 @@ +package com.currency.appengine.controller; + +import com.currency.appengine.domain.CustomerReq; +import com.currency.appengine.service.common.CommonServices; +import com.currency.appengine.service.system.CustomerService; +import com.currency.appengine.utils.Result; +import java.util.Map; +import java.util.Objects; +import org.springframework.beans.factory.annotation.Autowired; +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; + +/** + * 公开接口 + * + */ +@RestController +@RequestMapping("/open") +public class OpenController { + + @Autowired + private CustomerService customerService; + @Autowired + private CommonServices commonServices; + + @PostMapping("/add") + public Result add(@RequestBody Map obj) { + CustomerReq customerReq = new CustomerReq(); + customerReq.setCustomerName(obj.get("name").toString()); + if (Objects.nonNull(obj.get("remark"))) { + customerReq.setRemarks(obj.get("remark").toString()); + } + if (Objects.nonNull(obj.get("phone"))) { + customerReq.setContactPhone(obj.get("phone").toString()); + } + customerReq.setStatus(1); + customerReq.setIsAuto(true); + customerReq.setSettlementCurrency("CNY"); + customerReq.setTax("text2"); + customerReq.setIsTaxIncluded(1); + customerReq.setCreateBy("超级管理员"); + return Result.suc(customerService.AsynCustomer(customerReq)); + } +} diff --git a/appengine/src/main/java/com/currency/appengine/controller/SystemController.java b/appengine/src/main/java/com/currency/appengine/controller/SystemController.java index c807704..a9d7784 100644 --- a/appengine/src/main/java/com/currency/appengine/controller/SystemController.java +++ b/appengine/src/main/java/com/currency/appengine/controller/SystemController.java @@ -3,7 +3,9 @@ package com.currency.appengine.controller; import cn.hutool.captcha.CaptchaUtil; import cn.hutool.captcha.LineCaptcha; import com.currency.appengine.annotation.CheckToken; +import com.currency.appengine.domain.CustomerReq; import com.currency.appengine.domain.system.SysParam; +import com.currency.appengine.service.system.CustomerService; import com.currency.appengine.service.system.SysMenuService; import com.currency.appengine.service.system.SysParamService; import com.currency.appengine.service.system.SysRoleService; @@ -12,16 +14,20 @@ import com.currency.appengine.utils.JsonUtil; import com.currency.appengine.utils.ReqParamsUtil; import com.currency.appengine.utils.Result; import com.currency.appengine.utils.StringUtil; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.*; - +import java.util.ArrayList; +import java.util.List; +import java.util.Map; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; /** * @date 2021/07/25 @@ -29,6 +35,7 @@ import java.util.Map; @RestController @RequestMapping("/sys/authority") public class SystemController { + @Autowired SysUserService sysUserService; @Autowired @@ -37,10 +44,12 @@ public class SystemController { SysRoleService sysRoleService; @Autowired SysParamService sysParamService; + @Autowired + CustomerService customerService; /* - * 获取图形验证码 - * */ + * 获取图形验证码 + * */ @RequestMapping("/kaptcha") public void getKaptchaImage(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpSession session = request.getSession(); @@ -64,6 +73,7 @@ public class SystemController { /** * 登陆 + * * @param params * @return */ @@ -82,10 +92,10 @@ public class SystemController { if (StringUtil.notEmpty(session.getAttribute("KAPTCHA_SESSION_KEY"))) { String vercode = String.valueOf(params.get("verifycode")); if (!code.equals(vercode)) { - return Result.fail(-14,"验证码错误"); + return Result.fail(-14, "验证码错误"); } } else { - return Result.fail(-14,"验证码错误"); + return Result.fail(-14, "验证码错误"); } return sysUserService.userLogin(params); } @@ -164,6 +174,7 @@ public class SystemController { /** * 角色操作 + * * @return */ @GetMapping("/role/list/all") @@ -235,12 +246,13 @@ public class SystemController { /** * 菜单操作 + * * @return */ @GetMapping("/menu/list/all") @CheckToken public Result menuListAll() { - return sysMenuService.getRouterListAll(); + return sysMenuService.getRouterListAll(); } @PostMapping("/menu/add") @@ -320,4 +332,13 @@ public class SystemController { public Result cacheUpdate() { return sysParamService.cacheUpdate(); } + + + @PostMapping("/customer/add") + @CheckToken + public Result addCustomer(HttpServletRequest request, @RequestBody CustomerReq customerReq) { + String userId = StringUtil.objectToString(request.getAttribute("openid")); + customerReq.setUserId(userId); + return Result.suc(customerService.addCustomer(customerReq)); + } } diff --git a/appengine/src/main/java/com/currency/appengine/domain/AysnCustomer.java b/appengine/src/main/java/com/currency/appengine/domain/AysnCustomer.java new file mode 100644 index 0000000..43e7a91 --- /dev/null +++ b/appengine/src/main/java/com/currency/appengine/domain/AysnCustomer.java @@ -0,0 +1,41 @@ +package com.currency.appengine.domain; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; + +/** + * 同步数据到o1 + * + * @author zk + * @date 2024/9/30 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@ToString +@Builder +public class AysnCustomer { + /** + * 客户名称 + */ + private String name; + /** + * 手机号 + */ + private String phone; + /** + * 邮箱 + */ + private String email; + /** + * 备注 + */ + private String remark; + /** + * 创建时间(时间戳) + */ +// private Long createTime; +} diff --git a/appengine/src/main/java/com/currency/appengine/domain/CustomerReq.java b/appengine/src/main/java/com/currency/appengine/domain/CustomerReq.java new file mode 100644 index 0000000..664dc98 --- /dev/null +++ b/appengine/src/main/java/com/currency/appengine/domain/CustomerReq.java @@ -0,0 +1,75 @@ +package com.currency.appengine.domain; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; + +/** + * 客户请求信息 + * + * @author zk + * @date 2024/10/23 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@ToString +public class CustomerReq { + + private Long id; + /** + * 地址 + */ + private String address; + /** + * 联系人 + */ + private String contactPerson; + /** + * 联系方式 + */ + private String contactPhone; + /** + * 联系人编码 + */ + private String customerCode; + /** + * 联系人名称 + */ + private String customerName; + /** + * 是否自动编码 + */ + private Boolean isAuto; + /** + * 是否含税 + */ + private Integer isTaxIncluded; + /** + * 备注 + */ + private String remarks; + /** + * 结算货币类型 + */ + private String settlementCurrency; + /** + * 状态 + */ + private Integer status; + /** + * 税率 + */ + private String tax; + /** + * 创建人 + */ + private String createBy; + /** + * 创建人id + */ + private String userId; +} diff --git a/appengine/src/main/java/com/currency/appengine/filter/RepeatedlyReadFilter.java b/appengine/src/main/java/com/currency/appengine/filter/RepeatedlyReadFilter.java new file mode 100644 index 0000000..6ef859c --- /dev/null +++ b/appengine/src/main/java/com/currency/appengine/filter/RepeatedlyReadFilter.java @@ -0,0 +1,21 @@ +package com.currency.appengine.filter;//package cn.iocoder.yudao.module.open.filter; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +public class RepeatedlyReadFilter implements Filter { + + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { + ServletRequest requestWrapper = null; + if(servletRequest instanceof HttpServletRequest) { + requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest); + } + if(requestWrapper == null) { + filterChain.doFilter(servletRequest, servletResponse); + } else { + filterChain.doFilter(requestWrapper, servletResponse); + } + } +} \ No newline at end of file diff --git a/appengine/src/main/java/com/currency/appengine/filter/RequestWrapper.java b/appengine/src/main/java/com/currency/appengine/filter/RequestWrapper.java new file mode 100644 index 0000000..36a6c42 --- /dev/null +++ b/appengine/src/main/java/com/currency/appengine/filter/RequestWrapper.java @@ -0,0 +1,85 @@ +package com.currency.appengine.filter; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.io.*; + +public class RequestWrapper extends HttpServletRequestWrapper { + private final String body; + + public RequestWrapper(HttpServletRequest request) { + super(request); + StringBuilder stringBuilder = new StringBuilder(); + BufferedReader bufferedReader = null; + InputStream inputStream = null; + try { + inputStream = request.getInputStream(); + if (inputStream != null) { + bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + char[] charBuffer = new char[128]; + int bytesRead = -1; + while ((bytesRead = bufferedReader.read(charBuffer)) > 0) { + stringBuilder.append(charBuffer, 0, bytesRead); + } + } else { + stringBuilder.append(""); + } + } catch (IOException ex) { + + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + if (bufferedReader != null) { + try { + bufferedReader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + body = stringBuilder.toString(); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes()); + return new ServletInputStream() { + @Override + public boolean isFinished() { + return false; + } + + @Override + public boolean isReady() { + return false; + } + + @Override + public void setReadListener(ReadListener readListener) { + } + + @Override + public int read() throws IOException { + return byteArrayInputStream.read(); + } + }; + + } + + @Override + public BufferedReader getReader() throws IOException { + return new BufferedReader(new InputStreamReader(this.getInputStream())); + } + + public String getBody() { + return this.body; + } + +} \ No newline at end of file diff --git a/appengine/src/main/java/com/currency/appengine/handler/GlobalInterceptor.java b/appengine/src/main/java/com/currency/appengine/handler/GlobalInterceptor.java index 27c45b9..d6a34f3 100644 --- a/appengine/src/main/java/com/currency/appengine/handler/GlobalInterceptor.java +++ b/appengine/src/main/java/com/currency/appengine/handler/GlobalInterceptor.java @@ -56,7 +56,7 @@ public class GlobalInterceptor implements HandlerInterceptor { log.info(">>>>>>>Call before request processing (check token) "); Map headers = HttpUtil.getHeadersInfo(request); - String token = headers.get("authorization");; + String token = headers.get("authorization"); if (token == null || token.isEmpty()) { HttpUtil.returnJson(response, JsonUtil.generate(Result.fail(-2, "result.not_token"))); log.info(">>>>>>>check token fail"); diff --git a/appengine/src/main/java/com/currency/appengine/handler/SignInterceptor.java b/appengine/src/main/java/com/currency/appengine/handler/SignInterceptor.java new file mode 100644 index 0000000..be0e8b3 --- /dev/null +++ b/appengine/src/main/java/com/currency/appengine/handler/SignInterceptor.java @@ -0,0 +1,144 @@ +package com.currency.appengine.handler; + +import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.hutool.json.JSONUtil; +import com.currency.appengine.filter.RequestWrapper; +import com.currency.appengine.utils.IpUtils; +import com.currency.appengine.utils.Result; +import com.currency.appengine.utils.SignUtil; +import java.io.BufferedReader; +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.servlet.HandlerInterceptor; + +@Slf4j +public class SignInterceptor implements HandlerInterceptor { + + private static final String FORMAT = "yyyy-MM-dd HH:mm:ss"; // + private static final String ACCESS_KEY = "access-key";//调用者身份唯一标识 + private static final String TIMESTAMP = "time-stamp";//时间戳 + private static final String SIGN = "sign";//签名 + private static final String NONCE = "nonce";//随机值 + + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + // 是否开启 + String enable = SpringUtil.getProperty("open.api.enable"); + if (!Boolean.parseBoolean(enable)) { + return false; + } + if (checkSign(request, response)) {//签名认证 + return HandlerInterceptor.super.preHandle(request, response, handler); + } + return false; + } + + /** + * 验证签名 + * + * @param request + * @param response + * @return + * @throws Exception + */ + private boolean checkSign(HttpServletRequest request, HttpServletResponse response) throws IOException { + response.setContentType("application/json"); + response.setCharacterEncoding("utf8"); + String ip = IpUtils.getIpAddr(request); + log.info("开放接口 访问时间:{} ,IP:{} ,访问接口: {}", LocalDateTime.now().format(DateTimeFormatter.ofPattern(FORMAT)), ip, request.getRequestURL()); + String accessKey = request.getHeader(ACCESS_KEY); + String timestamp = request.getHeader(TIMESTAMP); + String nonce = request.getHeader(NONCE); + String sign = request.getHeader(SIGN); + if (CharSequenceUtil.isBlank(accessKey)) { + response.getWriter().write(JSONUtil.toJsonStr(Result.fail(403, "accessKey无效"))); + log.error( + "开放接口请求失败,时间:{} ,IP:{} ,访问接口:{} 错误信息:accessKey无效", LocalDateTime.now().format(DateTimeFormatter.ofPattern(FORMAT)), ip, + request.getRequestURL() + ); + return false; + } + if (CharSequenceUtil.isBlank(sign)) { + response.getWriter().write(JSONUtil.toJsonStr(Result.fail(403, "签名无效"))); + log.error( + "开放接口请求失败,时间:{},IP:{},访问接口:{}错误信息:签名无效", LocalDateTime.now().format(DateTimeFormatter.ofPattern(FORMAT)), ip, + request.getRequestURL() + ); + return false; + } + if (CharSequenceUtil.isBlank(timestamp)) { + response.getWriter().write(JSONUtil.toJsonStr(Result.fail(403, "时间戳无效"))); + log.error( + "开放接口请求失败,时间:{},IP:{},访问接口:{}错误信息:时间戳无效", LocalDateTime.now().format(DateTimeFormatter.ofPattern(FORMAT)), ip, + request.getRequestURL() + ); + return false; + } + Map hashMap = new HashMap<>(); + String queryStrings = request.getQueryString();//获取url后边拼接的参数 + if (Objects.nonNull(queryStrings)) { + for (String queryString : queryStrings.split("&")) { + String[] param = queryString.split("="); + if (param.length == 2) { + hashMap.put(param[0], param[1]); + } + } + } + hashMap.put(ACCESS_KEY, accessKey); + hashMap.put(TIMESTAMP, timestamp); + if (CharSequenceUtil.isNotBlank(nonce)) { + hashMap.put(NONCE, nonce); + } + // 判断时间戳是否超时,单位毫秒 + String timeOut = SpringUtil.getProperty("open.api.time-out"); + String secretKey = SpringUtil.getProperty("open.api.secret-key"); + if (CharSequenceUtil.isBlank(secretKey)) { + response.getWriter().write(JSONUtil.toJsonStr(Result.fail(403, "secretKey无效"))); + log.error( + "开放接口请求失败,时间:{},IP:{},访问接口:{}错误信息:secretKey无效", LocalDateTime.now().format(DateTimeFormatter.ofPattern(FORMAT)), ip, + request.getRequestURL() + ); + return false; + } + if (CharSequenceUtil.isNotBlank(timeOut)) { + long timeOutLong = Long.parseLong(timeOut); + long currentTimeMillis = System.currentTimeMillis(); + long timestampLong = Long.parseLong(timestamp); + if (currentTimeMillis - timestampLong > timeOutLong) { + response.getWriter().write(JSONUtil.toJsonStr(Result.fail(403, "时间戳超时"))); + log.error( + "开放接口请求失败,时间:{},IP:{},访问接口:{}错误信息:时间戳超时,", LocalDateTime.now().format(DateTimeFormatter.ofPattern(FORMAT)), ip, + request.getRequestURL() + ); + return false; + } + } + // 验证签名,获取request中的参数,并生成签名 +// String body = new RequestWrapper(request).getBody(); + RequestWrapper wrapper = new RequestWrapper(request); + BufferedReader reader = wrapper.getReader(); + String body = reader.lines().reduce("", (a, b) -> a + b); + if (CharSequenceUtil.isNotBlank(body)) { + Map map = JSONUtil.parseObj(body); + hashMap.putAll(map); + } + if (!SignUtil.signValidate(hashMap, secretKey, sign)) {//认证失败 + response.getWriter().write(JSONUtil.toJsonStr(Result.fail(403, "认证失败"))); + log.error( + "开放接口请求失败,时间:{},IP:{},访问接口:{}错误信息:认证失败", LocalDateTime.now().format(DateTimeFormatter.ofPattern(FORMAT)), ip, + request.getRequestURL() + ); + return false; + } + return true; + } +} diff --git a/appengine/src/main/java/com/currency/appengine/mapper/system/CustomerMapper.java b/appengine/src/main/java/com/currency/appengine/mapper/system/CustomerMapper.java new file mode 100644 index 0000000..ef85f0b --- /dev/null +++ b/appengine/src/main/java/com/currency/appengine/mapper/system/CustomerMapper.java @@ -0,0 +1,30 @@ +package com.currency.appengine.mapper.system; + +import com.currency.appengine.domain.CustomerReq; +import org.apache.ibatis.annotations.Insert; +import org.apache.ibatis.annotations.Options; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Update; + +/** + * @author zk + * @date 2024/10/23 + */ +public interface CustomerMapper { + + @Insert( + "insert into mini_customer_info(address,create_by, contact_person, contact_phone, customer_name, is_tax_included, remarks, settlement_currency, status, tax) values(#{address},#{createBy}, #{contactPerson}, #{contactPhone}, #{customerName}, #{isTaxIncluded}, #{remarks}, #{settlementCurrency}, #{status}, #{tax});" + ) + @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id") + int insertCustomer(CustomerReq customerReq); + + @Select( + "SELECT user_name FROM sys_user WHERE user_id = #{userId}" + ) + String selectUserById(String userId); + + @Update( + "update mini_customer_info set customer_code = #{customerCode} where id = #{id}" + ) + void updateCodeById(CustomerReq customerReq); +} diff --git a/appengine/src/main/java/com/currency/appengine/service/system/CustomerService.java b/appengine/src/main/java/com/currency/appengine/service/system/CustomerService.java new file mode 100644 index 0000000..bef6579 --- /dev/null +++ b/appengine/src/main/java/com/currency/appengine/service/system/CustomerService.java @@ -0,0 +1,18 @@ +package com.currency.appengine.service.system; + +import com.currency.appengine.domain.CustomerReq; + +/** + * @author zk + * @date 2024/10/23 + */ +public interface CustomerService { + /** + * 添加客户信息 + */ + int addCustomer(CustomerReq customerReq); + /** + * 同步客户信息 + */ + int AsynCustomer(CustomerReq customerReq); +} diff --git a/appengine/src/main/java/com/currency/appengine/service/system/imp/CustomerServiceImpl.java b/appengine/src/main/java/com/currency/appengine/service/system/imp/CustomerServiceImpl.java new file mode 100644 index 0000000..67c4d69 --- /dev/null +++ b/appengine/src/main/java/com/currency/appengine/service/system/imp/CustomerServiceImpl.java @@ -0,0 +1,109 @@ +package com.currency.appengine.service.system.imp; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import cn.hutool.json.JSONUtil; +import com.currency.appengine.domain.AysnCustomer; +import com.currency.appengine.domain.CustomerReq; +import com.currency.appengine.mapper.system.CustomerMapper; +import com.currency.appengine.service.common.CommonServices; +import com.currency.appengine.service.system.CustomerService; +import com.currency.appengine.utils.SecurityUtils; +import com.currency.appengine.utils.SignUtil; +import java.util.HashMap; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * @author zk + * @date 2024/10/23 + */ +@Slf4j +@Service +public class CustomerServiceImpl implements CustomerService { + + @Autowired + private CustomerMapper customerMapper; + @Autowired + private CommonServices commonServices; + + /** + * 添加客户信息 + * + */ + @Override + public int addCustomer(CustomerReq customerReq) { + if (Boolean.TRUE.equals(customerReq.getIsAuto())){ + customerReq.setCustomerCode(commonServices.getCode("CustomerCode", null)); + } + String user = customerMapper.selectUserById(customerReq.getUserId()); + customerReq.setCreateBy(user); + int i = customerMapper.insertCustomer(customerReq); + customerReq.setCustomerCode(String.valueOf(customerReq.getId())); + customerMapper.updateCodeById(customerReq); + try { + aysnO2(customerReq); + } catch (Exception e) { + log.warn("同步客户信息异常", e); + } + return i; + } + + /** + * 同步客户信息 + * + * @param customerReq + */ + @Override + public int AsynCustomer(CustomerReq customerReq) { + int i = customerMapper.insertCustomer(customerReq); + customerReq.setCustomerCode(String.valueOf(customerReq.getId())); + customerMapper.updateCodeById(customerReq); + return i; + } + /** + * 同步到O2客户信息 + */ + private static void aysnO2(CustomerReq req) { + String property = SpringUtil.getProperty("o2.sync.enabled"); + Boolean res = Boolean.valueOf(property); + if (Boolean.TRUE.equals(res)) { + String accessKey = SpringUtil.getProperty("o2.sync.access-key"); + String secretKey = SpringUtil.getProperty("o2.sync.secret-key"); + String url = SpringUtil.getProperty("o2.sync.create-url"); + Map map = new HashMap<>(); + AysnCustomer customer = new AysnCustomer(); + customer.setName(req.getCustomerName()); + customer.setPhone(req.getContactPhone()); + customer.setRemark(req.getRemarks()); +// customer.setCreateTime(crmCustomer.getCreateTime().getTime()); + Map toMap = BeanUtil.beanToMap(customer, false, true); + long timestamp = System.currentTimeMillis(); + map.put("time-stamp", String.valueOf(timestamp)); + map.put("access-key", accessKey); + map.put("tenant-id", "1"); + toMap.put("time-stamp", String.valueOf(timestamp)); + toMap.put("access-key", accessKey); + String sign = SignUtil.getSign(toMap, secretKey); + map.put("sign", sign); + HttpResponse response = null; + try { + response = HttpRequest.post(url) + .addHeaders(map) + .body(JSONUtil.toJsonStr(customer)) + .execute(); + } catch (Exception e) { + log.warn("同步O2客户信息异常", e); + return; + } + String body = response.body(); + if (response.isOk()) { + log.info("同步O2成功"); + } + } + } +} diff --git a/appengine/src/main/java/com/currency/appengine/utils/IpUtils.java b/appengine/src/main/java/com/currency/appengine/utils/IpUtils.java new file mode 100644 index 0000000..ecc5fa4 --- /dev/null +++ b/appengine/src/main/java/com/currency/appengine/utils/IpUtils.java @@ -0,0 +1,224 @@ +package com.currency.appengine.utils; + +import cn.hutool.core.util.StrUtil; +import java.util.Objects; +import javax.servlet.http.HttpServletRequest; +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * 获取IP方法 + * + * @author ruoyi + */ +public class IpUtils { + /** + * 获取客户端IP + * + * @param request 请求对象 + * @return IP地址 + */ + public static String getIpAddr(HttpServletRequest request) { + if (request == null) { + return "unknown"; + } + String ip = request.getHeader("x-forwarded-for"); + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("X-Forwarded-For"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("X-Real-IP"); + } + + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + + return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip); + } + + /** + * 检查是否为内部IP地址 + * + * @param ip IP地址 + * @return 结果 + */ + public static boolean internalIp(String ip) { + byte[] addr = textToNumericFormatV4(ip); + return internalIp(addr) || "127.0.0.1".equals(ip); + } + + /** + * 检查是否为内部IP地址 + * + * @param addr byte地址 + * @return 结果 + */ + private static boolean internalIp(byte[] addr) { + if (Objects.isNull(addr) || addr.length < 2) { + return true; + } + final byte b0 = addr[0]; + final byte b1 = addr[1]; + // 10.x.x.x/8 + final byte SECTION_1 = 0x0A; + // 172.16.x.x/12 + final byte SECTION_2 = (byte) 0xAC; + final byte SECTION_3 = (byte) 0x10; + final byte SECTION_4 = (byte) 0x1F; + // 192.168.x.x/16 + final byte SECTION_5 = (byte) 0xC0; + final byte SECTION_6 = (byte) 0xA8; + switch (b0) { + case SECTION_1: + return true; + case SECTION_2: + if (b1 >= SECTION_3 && b1 <= SECTION_4) { + return true; + } + case SECTION_5: + switch (b1) { + case SECTION_6: + return true; + } + default: + return false; + } + } + + /** + * 将IPv4地址转换成字节 + * + * @param text IPv4地址 + * @return byte 字节 + */ + public static byte[] textToNumericFormatV4(String text) { + if (text.length() == 0) { + return null; + } + + byte[] bytes = new byte[4]; + String[] elements = text.split("\\.", -1); + try { + long l; + int i; + switch (elements.length) { + case 1: + l = Long.parseLong(elements[0]); + if ((l < 0L) || (l > 4294967295L)) { + return null; + } + bytes[0] = (byte) (int) (l >> 24 & 0xFF); + bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF); + bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 2: + l = Integer.parseInt(elements[0]); + if ((l < 0L) || (l > 255L)) { + return null; + } + bytes[0] = (byte) (int) (l & 0xFF); + l = Integer.parseInt(elements[1]); + if ((l < 0L) || (l > 16777215L)) { + return null; + } + bytes[1] = (byte) (int) (l >> 16 & 0xFF); + bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 3: + for (i = 0; i < 2; ++i) { + l = Integer.parseInt(elements[i]); + if ((l < 0L) || (l > 255L)) { + return null; + } + bytes[i] = (byte) (int) (l & 0xFF); + } + l = Integer.parseInt(elements[2]); + if ((l < 0L) || (l > 65535L)) { + return null; + } + bytes[2] = (byte) (int) (l >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 4: + for (i = 0; i < 4; ++i) { + l = Integer.parseInt(elements[i]); + if ((l < 0L) || (l > 255L)) { + return null; + } + bytes[i] = (byte) (int) (l & 0xFF); + } + break; + default: + return null; + } + } catch (NumberFormatException e) { + return null; + } + return bytes; + } + + /** + * 获取IP地址 + * + * @return 本地IP地址 + */ + public static String getHostIp() { + try { + return InetAddress.getLocalHost().getHostAddress(); + } catch (UnknownHostException e) { + } + return "127.0.0.1"; + } + + /** + * 获取主机名 + * + * @return 本地主机名 + */ + public static String getHostName() { + try { + return InetAddress.getLocalHost().getHostName(); + } catch (UnknownHostException e) { + } + return "未知"; + } + + /** + * 从多级反向代理中获得第一个非unknown IP地址 + * + * @param ip 获得的IP地址 + * @return 第一个非unknown IP地址 + */ + public static String getMultistageReverseProxyIp(String ip) { + // 多级反向代理检测 + if (ip != null && ip.indexOf(",") > 0) { + final String[] ips = ip.trim().split(","); + for (String subIp : ips) { + if (!isUnknown(subIp)) { + ip = subIp; + break; + } + } + } + return ip; + } + + /** + * 检测给定字符串是否为未知,多用于检测HTTP请求相关 + * + * @param checkString 被检测的字符串 + * @return 是否未知 + */ + public static boolean isUnknown(String checkString) { + return StrUtil.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString); + } +} \ No newline at end of file diff --git a/appengine/src/main/java/com/currency/appengine/utils/SignUtil.java b/appengine/src/main/java/com/currency/appengine/utils/SignUtil.java new file mode 100644 index 0000000..8d281dc --- /dev/null +++ b/appengine/src/main/java/com/currency/appengine/utils/SignUtil.java @@ -0,0 +1,66 @@ +package com.currency.appengine.utils; + +import cn.hutool.core.net.URLEncodeUtil; +import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.crypto.digest.MD5; + +import java.util.*; + +/** + * 签名工具类 + * + * @author zk + */ +public class SignUtil { + + /** + * 签名算法 + * 1. 计算步骤 + * 用于计算签名的参数在不同接口之间会有差异,但算法过程固定如下4个步骤。 + * 将请求参数对按key进行字典升序排序,得到有序的参数对列表N + * 将列表N中的参数对按URL键值对的格式拼接成字符串,得到字符串T(如:key1=value1&key2=value2),URL键值拼接过程value部分需要URL编码,URL编码算法用大写字母,例如%E8,而不是小写%e8 + * 将应用密钥以app_key为键名,组成URL键值拼接到字符串T末尾,得到字符串S(如:key1=value1&key2=value2&app_key=密钥) + * 对字符串S进行MD5运算,将得到的MD5值所有字符转换成大写,得到接口请求签名 + * 2. 注意事项 + * 不同接口要求的参数对不一样,计算签名使用的参数对也不一样 + * 参数名区分大小写,参数值为空不参与签名 + * URL键值拼接过程value部分需要URL编码 + * + * @return 签名字符串 + */ + public static String getSign(Map map, String secretKey) { + List> infoIds = new ArrayList<>(map.entrySet()); + infoIds.sort(Map.Entry.comparingByKey()); + StringBuilder sb = new StringBuilder(); + for (Map.Entry m : infoIds) { + if (Objects.nonNull(m.getValue()) && CharSequenceUtil.isNotBlank(m.getValue().toString())) { + sb.append(m.getKey()).append("=").append(URLEncodeUtil.encodeAll(m.getValue().toString())).append("&"); + } + } + sb.append("secret-key=").append(secretKey); + return MD5.create().digestHex(sb.toString()).toUpperCase(); + } + + + //获取随机值,防止重复提交,由于使用了时间戳,暂时不使用 + public static String getNonceStr(int length) { + //生成随机字符串 + String str = "zxcvbnmlkjhgfdsaqwertyuiopQWERTYUIOPASDFGHJKLZXCVBNM1234567890"; + Random random = new Random(); + StringBuilder randomStr = new StringBuilder(); + // 设置生成字符串的长度,用于循环 + for (int i = 0; i < length; ++i) { + //从62个的数字或字母中选择 + int number = random.nextInt(62); + //将产生的数字通过length次承载到sb中 + randomStr.append(str.charAt(number)); + } + return randomStr.toString(); + } + + //签名验证方法 + public static boolean signValidate(Map map, String secretKey, String sign) { + String mySign = getSign(map, secretKey); + return mySign.equals(sign); + } +} diff --git a/appengine/src/main/resources/application.yml b/appengine/src/main/resources/application.yml index 78826ea..f5e4a53 100644 --- a/appengine/src/main/resources/application.yml +++ b/appengine/src/main/resources/application.yml @@ -1,8 +1,8 @@ spring: profiles: - # active: local # test 本地 - # active: dev # test 测试 - active: pro # pro 生产 + active: local # test 本地 +# active: dev # test 测试 +# active: pro # pro 生产 # 通用配置 servlet: @@ -52,3 +52,20 @@ knife4j: deployfront: aes-key: hdTQ5SPTs3mp5Xt7ULeaxw== plaintext: '@mkxy' + +# 开放接口超时时间(毫秒级)时间戳 +open: + api: + enable: true + time-out: 10000000 + secret-key: alLv8CPzZjM6EAgvKqvVjf4kOuTPSmoQTbgK25jz +# o2 同步配置 +o2: + sync: + access-key: 8ceeH0H6w4ZIR5nHSDB7 + # 密钥 + secret-key: alLv8CPzZjM6EAgvKqvVjf4kOuTPSmoQTbgK25jz + # 是否启用同步 + enabled: true + # 同步新增o2的客户api地址 + create-url: http://127.0.0.1:8182/open/api/add