添加小程序后端服务

This commit is contained in:
keivn 2021-12-18 22:05:56 +08:00
parent ec838ac8c3
commit ae57510418
51 changed files with 2337 additions and 0 deletions

120
carpool-wx-api/pom.xml Normal file
View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ruoyi</artifactId>
<groupId>com.ruoyi</groupId>
<version>3.7.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>carpool-wx-api</artifactId>
<dependencies>
<!-- SpringBoot Web容器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- SpringBoot 拦截器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- swagger2-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<!--防止进入swagger页面报类型转换错误排除2.9.2中的引用手动增加1.5.21版本-->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.5.21</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
<version>1.5.21</version>
</dependency>
<!-- swagger2-UI-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.9.3</version>
</dependency>
<!-- 糊涂工具包 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java-pay-spring-boot-starter</artifactId>
<version>3.5.4.B</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java-miniapp-spring-boot-starter</artifactId>
<version>3.5.4.B</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>3.5.5.B</version>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>2.0.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.1.1.RELEASE</version>
<configuration>
<fork>true</fork> <!-- 如果没有该配置devtools不会生效 -->
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
<warName>${project.artifactId}</warName>
</configuration>
</plugin>
</plugins>
<finalName>${project.artifactId}</finalName>
</build>
</project>

View File

@ -0,0 +1,27 @@
package vip.carpool;
import com.github.xiaoymin.swaggerbootstrapui.annotations.EnableSwaggerBootstrapUI;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
System.out.println("(♥◠‿◠)ノ゙ api网关启动成功 ლ(´ڡ`ლ)゙ \n" +
" .-------. ____ __ \n" +
" | _ _ \\ \\ \\ / / \n" +
" | ( ' ) | \\ _. / ' \n" +
" |(_ o _) / _( )_ .' \n" +
" | (_,_).' __ ___(_ o _)' \n" +
" | |\\ \\ | || |(_,_)' \n" +
" | | \\ `' /| `-' / \n" +
" | | \\ / \\ / \n" +
" ''-' `'-' `-..-' ");
}
}

View File

@ -0,0 +1,54 @@
package vip.carpool.api.gateway.common.api;
public enum ApiCode {
SUCCESS(200, "操作成功"),
UNAUTHORIZED(401, "非法访问"),
NOT_PERMISSION(403, "没有权限"),
NOT_FOUND(404, "你请求的路径不存在"),
FAIL(500, "操作失败"),
SYSTEM_EXCEPTION(5000,"系统异常!"),
PARAMETER_EXCEPTION(5001,"请求参数校验异常"),
PARAMETER_PARSE_EXCEPTION(5002,"请求参数解析异常"),
HTTP_MEDIA_TYPE_EXCEPTION(5003,"HTTP Media 类型异常"),
SYSTEM_LOGIN_EXCEPTION(5005,"系统登录异常"),
;
private final int code;
private final String msg;
ApiCode(final int code, final String msg) {
this.code = code;
this.msg = msg;
}
public static ApiCode getApiCode(int code) {
ApiCode[] ecs = ApiCode.values();
for (ApiCode ec : ecs) {
if (ec.getCode() == code) {
return ec;
}
}
return SUCCESS;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}

View File

@ -0,0 +1,47 @@
package vip.carpool.api.gateway.common.api;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ApiController {
/**
* <p>
* 请求成功
* </p>
*
* @param data 数据内容
* @param <T> 对象泛型
* @return
*/
protected <T> ApiResult<T> ok(T data) {
return ApiResult.ok(data);
}
/**
* <p>
* 请求失败
* </p>
*
* @param msg 提示内容
* @return
*/
protected ApiResult<Object> fail(String msg) {
return ApiResult.fail(msg);
}
/**
* <p>
* 请求失败
* </p>
*
* @param apiCode 请求错误码
* @return
*/
protected ApiResult<Object> fail(ApiCode apiCode) {
return ApiResult.fail(apiCode);
}
}

View File

@ -0,0 +1,106 @@
package vip.carpool.api.gateway.common.api;
import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.Accessors;
import org.apache.commons.lang3.StringUtils;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Data
@Accessors(chain = true)
@Builder
@AllArgsConstructor
public class ApiResult<T> implements Serializable {
private int code;
private T data;
private String msg;
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date time;
public ApiResult() {
}
public static ApiResult result(boolean flag) {
if (flag) {
return ok();
}
return fail("");
}
public static ApiResult result(ApiCode apiCode) {
return result(apiCode, null);
}
public static ApiResult result(ApiCode apiCode, String msg) {
return result(apiCode, msg);
}
public static ApiResult result(ApiCode apiCode, Object data) {
return result(apiCode, null, data);
}
public static ApiResult result(ApiCode apiCode, String msg, Object data) {
String message = apiCode.getMsg();
if (StringUtils.isNotBlank(msg)) {
message = msg;
}
return ApiResult.builder()
.code(apiCode.getCode())
.msg(message)
.data(data)
.time(new Date())
.build();
}
public static ApiResult ok() {
return ok(null);
}
public static ApiResult ok(Object data) {
return result(ApiCode.SUCCESS, data);
}
public static ApiResult ok(String key, Object value) {
Map<String, Object> map = new HashMap<>();
map.put(key, value);
return ok(map);
}
public static ApiResult fail(ApiCode apiCode) {
return result(apiCode, null);
}
public static ApiResult fail(String msg) {
return result(ApiCode.FAIL, msg, null);
}
public static ApiResult fail(ApiCode apiCode, Object data) {
if (ApiCode.SUCCESS == apiCode) {
throw new RuntimeException("失败结果状态码不能为" + ApiCode.SUCCESS.getCode());
}
return result(apiCode, data);
}
public static ApiResult fail(String key, Object value) {
Map<String, Object> map = new HashMap<>();
map.put(key, value);
return result(ApiCode.FAIL, map);
}
}

View File

@ -0,0 +1,17 @@
package vip.carpool.api.gateway.common.builder;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author 刘荣华
*/
public abstract class AbstractBuilder {
protected final Logger logger = LoggerFactory.getLogger(getClass());
public abstract WxMpXmlOutMessage build(String content,
WxMpXmlMessage wxMessage, WxMpService service);
}

View File

@ -0,0 +1,24 @@
package vip.carpool.api.gateway.common.builder;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutImageMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
/**
* @author 刘荣华
*/
public class ImageBuilder extends AbstractBuilder {
@Override
public WxMpXmlOutMessage build(String content, WxMpXmlMessage wxMessage,
WxMpService service) {
WxMpXmlOutImageMessage m = WxMpXmlOutMessage.IMAGE().mediaId(content)
.fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser())
.build();
return m;
}
}

View File

@ -0,0 +1,22 @@
package vip.carpool.api.gateway.common.builder;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutTextMessage;
/**
* @author 刘荣华
*/
public class TextBuilder extends AbstractBuilder {
@Override
public WxMpXmlOutMessage build(String content, WxMpXmlMessage wxMessage,
WxMpService service) {
WxMpXmlOutTextMessage m = WxMpXmlOutMessage.TEXT().content(content)
.fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser())
.build();
return m;
}
}

View File

@ -0,0 +1,46 @@
package vip.carpool.api.gateway.common.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
/**
* 跨域配置
*
* @author ruoyi
*/
@Configuration
public class CorsConfig {
// 设置允许跨域的源
private static String[] originsVal = new String[]{
"127.0.0.1:8006",
"localhost:8006",
"carpool.vip"
};
/**
* 跨域过滤器
*
* @return
*/
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
this.addAllowedOrigins(corsConfiguration);
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addAllowedOrigin("*");
source.registerCorsConfiguration("/**", corsConfiguration);
return new CorsFilter(source);
}
private void addAllowedOrigins(CorsConfiguration corsConfiguration) {
for (String origin : originsVal) {
corsConfiguration.addAllowedOrigin("http://" + origin);
corsConfiguration.addAllowedOrigin("https://" + origin);
}
}
}

View File

@ -0,0 +1,38 @@
package vip.carpool.api.gateway.common.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* @ClassName SwaggerConfiguration
* @author 刘荣华
**/
@Configuration
@EnableSwagger2
public class SwaggerConfiguration {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("vip.carpool.api.gateway"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("API网关")
.description("API网关")
.termsOfServiceUrl("https://carpool.vip/gateway/")
.version("1.0")
.build();
}
}

View File

@ -0,0 +1,113 @@
package vip.carpool.api.gateway.common.config;
import lombok.AllArgsConstructor;
import me.chanjar.weixin.mp.api.WxMpMessageRouter;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import vip.carpool.api.gateway.common.handler.*;
import java.util.List;
import java.util.stream.Collectors;
import static me.chanjar.weixin.common.api.WxConsts.EventType;
import static me.chanjar.weixin.common.api.WxConsts.EventType.SUBSCRIBE;
import static me.chanjar.weixin.common.api.WxConsts.EventType.UNSUBSCRIBE;
import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType;
import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType.EVENT;
import static me.chanjar.weixin.mp.constant.WxMpEventConstants.CustomerService.*;
import static me.chanjar.weixin.mp.constant.WxMpEventConstants.POI_CHECK_NOTIFY;
/**
* wechat mp configuration
*
* @author 刘荣华
*/
@AllArgsConstructor
@Configuration
@EnableConfigurationProperties(WxMpProperties.class)
public class WxMpConfiguration {
@Autowired
private LogHandler logHandler;
private NullHandler nullHandler;
private KfSessionHandler kfSessionHandler;
private StoreCheckNotifyHandler storeCheckNotifyHandler;
private LocationHandler locationHandler;
private MenuHandler menuHandler;
private MsgHandler msgHandler;
private UnsubscribeHandler unsubscribeHandler;
private SubscribeHandler subscribeHandler;
private ScanHandler scanHandler;
private WxMpProperties properties;
@Bean
public WxMpService wxMpService() {
// 代码里 getConfigs()处报错的同学请注意仔细阅读项目说明你的IDE需要引入lombok插件
final List<WxMpProperties.MpConfig> configs = this.properties.getConfigs();
if (configs == null) {
throw new RuntimeException("大哥拜托先看下项目首页的说明readme文件添加下相关配置注意别配错了");
}
WxMpService service = new WxMpServiceImpl();
service.setMultiConfigStorages(configs
.stream().map(a -> {
WxMpDefaultConfigImpl configStorage = new WxMpDefaultConfigImpl();
configStorage.setAppId(a.getAppId());
configStorage.setSecret(a.getSecret());
configStorage.setToken(a.getToken());
configStorage.setAesKey(a.getAesKey());
return configStorage;
}).collect(Collectors.toMap(WxMpDefaultConfigImpl::getAppId, a -> a, (o, n) -> o)));
return service;
}
@Bean
public WxMpMessageRouter messageRouter(WxMpService wxMpService) {
final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService);
// 记录所有事件的日志 异步执行
newRouter.rule().handler(this.logHandler).next();
// 接收客服会话管理事件
newRouter.rule().async(false).msgType(EVENT).event(KF_CREATE_SESSION)
.handler(this.kfSessionHandler).end();
newRouter.rule().async(false).msgType(EVENT).event(KF_CLOSE_SESSION)
.handler(this.kfSessionHandler).end();
newRouter.rule().async(false).msgType(EVENT).event(KF_SWITCH_SESSION)
.handler(this.kfSessionHandler).end();
// 门店审核事件
newRouter.rule().async(false).msgType(EVENT).event(POI_CHECK_NOTIFY).handler(this.storeCheckNotifyHandler).end();
// 自定义菜单事件
newRouter.rule().async(false).msgType(EVENT).event(EventType.CLICK).handler(this.menuHandler).end();
// 点击菜单连接事件
newRouter.rule().async(false).msgType(EVENT).event(EventType.VIEW).handler(this.nullHandler).end();
// 关注事件
newRouter.rule().async(false).msgType(EVENT).event(SUBSCRIBE).handler(this.subscribeHandler).end();
// 取消关注事件
newRouter.rule().async(false).msgType(EVENT).event(UNSUBSCRIBE).handler(this.unsubscribeHandler).end();
// 上报地理位置事件
newRouter.rule().async(false).msgType(EVENT).event(EventType.LOCATION).handler(this.locationHandler).end();
// 接收地理位置消息
newRouter.rule().async(false).msgType(XmlMsgType.LOCATION).handler(this.locationHandler).end();
// 扫码事件
newRouter.rule().async(false).msgType(EVENT).event(EventType.SCAN).handler(this.scanHandler).end();
// 默认
newRouter.rule().async(false).handler(this.msgHandler).end();
return newRouter;
}
}

View File

@ -0,0 +1,46 @@
package vip.carpool.api.gateway.common.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import vip.carpool.api.gateway.common.utils.JsonUtils;
import java.util.List;
/**
* wechat mp properties
*
* @author 刘荣华
*/
@Data
@ConfigurationProperties(prefix = "wx.mp")
public class WxMpProperties {
private List<MpConfig> configs;
@Data
public static class MpConfig {
/**
* 设置微信公众号的appid
*/
private String appId;
/**
* 设置微信公众号的app secret
*/
private String secret;
/**
* 设置微信公众号的token
*/
private String token;
/**
* 设置微信公众号的EncodingAESKey
*/
private String aesKey;
}
@Override
public String toString() {
return JsonUtils.toJson(this);
}
}

View File

@ -0,0 +1,26 @@
package vip.carpool.api.gateway.common.exception;
import lombok.Getter;
import org.springframework.http.HttpStatus;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
/**
* @ClassName 自定义异常
* @author 刘荣华
* @Date 2019/6/27
**/
@Getter
public class BadRequestException extends RuntimeException {
private Integer status = BAD_REQUEST.value();
public BadRequestException(String msg) {
super(msg);
}
public BadRequestException(HttpStatus status, String msg) {
super(msg);
this.status = status.value();
}
}

View File

@ -0,0 +1,63 @@
package vip.carpool.api.gateway.common.exception;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import vip.carpool.api.gateway.common.api.ApiCode;
import vip.carpool.api.gateway.common.api.ApiResult;
import java.util.Objects;
/**
* @ClassName 全局异常处理
* @author 刘荣华
* @Date 2019/6/27
**/
@Slf4j
@ControllerAdvice
public class GatewayExceptionHandler {
@ExceptionHandler(value = {Exception.class})
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public ApiResult allError(Exception exception) {
exception.printStackTrace();
return ApiResult.fail(ApiCode.FAIL, exception.getMessage());
}
/**
* 处理所有接口数据验证异常
*
* @param e
* @returns
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public ApiResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
e.printStackTrace();
String[] str = Objects.requireNonNull(e.getBindingResult().getAllErrors().get(0).getCodes())[1].split("\\.");
StringBuffer msg = new StringBuffer(str[1] + ":");
msg.append(e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
return ApiResult.fail(msg.toString());
}
/**
* 处理自定义异常
*
* @param e
* @return
*/
@ExceptionHandler(value = BadRequestException.class)
@ResponseBody
public ApiResult badRequestException(BadRequestException e) {
// 打印堆栈信息
e.printStackTrace();
log.error(e.getMessage());
return ApiResult.fail(e.getMessage());
}
}

View File

@ -0,0 +1,12 @@
package vip.carpool.api.gateway.common.handler;
import me.chanjar.weixin.mp.api.WxMpMessageHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author 刘荣华
*/
public abstract class AbstractHandler implements WxMpMessageHandler {
protected Logger logger = LoggerFactory.getLogger(getClass());
}

View File

@ -0,0 +1,25 @@
package vip.carpool.api.gateway.common.handler;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* @author 刘荣华
*/
@Component
public class KfSessionHandler extends AbstractHandler {
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,
Map<String, Object> context, WxMpService wxMpService,
WxSessionManager sessionManager) {
//TODO 对会话做处理
return null;
}
}

View File

@ -0,0 +1,44 @@
package vip.carpool.api.gateway.common.handler;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import org.springframework.stereotype.Component;
import vip.carpool.api.gateway.common.builder.TextBuilder;
import java.util.Map;
import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType;
/**
* @author 刘荣华
*/
@Component
public class LocationHandler extends AbstractHandler {
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,
Map<String, Object> context, WxMpService wxMpService,
WxSessionManager sessionManager) {
if (wxMessage.getMsgType().equals(XmlMsgType.LOCATION)) {
//TODO 接收处理用户发送的地理位置消息
try {
String content = "感谢反馈,您的的地理位置已收到!";
return new TextBuilder().build(content, wxMessage, null);
} catch (Exception e) {
this.logger.error("位置消息接收处理失败", e);
return null;
}
}
//上报地理位置事件
this.logger.info("上报地理位置,纬度 : {},经度 : {},精度 : {}",
wxMessage.getLatitude(), wxMessage.getLongitude(), String.valueOf(wxMessage.getPrecision()));
//TODO 可以将用户地理位置信息保存到本地数据库以便以后使用
return null;
}
}

View File

@ -0,0 +1,25 @@
package vip.carpool.api.gateway.common.handler;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import org.springframework.stereotype.Component;
import vip.carpool.api.gateway.common.utils.JsonUtils;
import java.util.Map;
/**
* @author 刘荣华
*/
@Component
public class LogHandler extends AbstractHandler {
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,
Map<String, Object> context, WxMpService wxMpService,
WxSessionManager sessionManager) {
this.logger.info("\n接收到请求消息内容{}", JsonUtils.toJson(wxMessage));
return null;
}
}

View File

@ -0,0 +1,35 @@
package vip.carpool.api.gateway.common.handler;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import org.springframework.stereotype.Component;
import java.util.Map;
import static me.chanjar.weixin.common.api.WxConsts.EventType;
/**
* @author 刘荣华
*/
@Component
public class MenuHandler extends AbstractHandler {
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,
Map<String, Object> context, WxMpService weixinService,
WxSessionManager sessionManager) {
String msg = String.format("type:%s, event:%s, key:%s",
wxMessage.getMsgType(), wxMessage.getEvent(),
wxMessage.getEventKey());
if (EventType.VIEW.equals(wxMessage.getEvent())) {
return null;
}
return WxMpXmlOutMessage.TEXT().content(msg)
.fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser())
.build();
}
}

View File

@ -0,0 +1,52 @@
package vip.carpool.api.gateway.common.handler;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import vip.carpool.api.gateway.common.builder.TextBuilder;
import vip.carpool.api.gateway.common.utils.JsonUtils;
import java.util.Map;
import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType;
/**
* @author 刘荣华
*/
@Component
public class MsgHandler extends AbstractHandler {
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,
Map<String, Object> context, WxMpService weixinService,
WxSessionManager sessionManager) {
if (!wxMessage.getMsgType().equals(XmlMsgType.EVENT)) {
//TODO 可以选择将消息保存到本地
}
//当用户输入关键词如你好客服并且有客服在线时把消息转发给在线客服
try {
if (StringUtils.startsWithAny(wxMessage.getContent(), "你好", "客服")
&& weixinService.getKefuService().kfOnlineList()
.getKfOnlineList().size() > 0) {
return WxMpXmlOutMessage.TRANSFER_CUSTOMER_SERVICE()
.fromUser(wxMessage.getToUser())
.toUser(wxMessage.getFromUser()).build();
}
} catch (WxErrorException e) {
e.printStackTrace();
}
//TODO 组装回复消息
String content = "收到信息内容:" + JsonUtils.toJson(wxMessage);
return new TextBuilder().build(content, wxMessage, weixinService);
}
}

View File

@ -0,0 +1,24 @@
package vip.carpool.api.gateway.common.handler;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* @author 刘荣华
*/
@Component
public class NullHandler extends AbstractHandler {
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,
Map<String, Object> context, WxMpService wxMpService,
WxSessionManager sessionManager) {
return null;
}
}

View File

@ -0,0 +1,24 @@
package vip.carpool.api.gateway.common.handler;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* @author 刘荣华
*/
@Component
public class ScanHandler extends AbstractHandler {
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMpXmlMessage, Map<String, Object> map,
WxMpService wxMpService, WxSessionManager wxSessionManager) throws WxErrorException {
// 扫码事件处理
return null;
}
}

View File

@ -0,0 +1,27 @@
package vip.carpool.api.gateway.common.handler;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* 门店审核事件处理
*
* @author 刘荣华
*/
@Component
public class StoreCheckNotifyHandler extends AbstractHandler {
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,
Map<String, Object> context, WxMpService wxMpService,
WxSessionManager sessionManager) {
// TODO 处理门店审核事件
return null;
}
}

View File

@ -0,0 +1,70 @@
package vip.carpool.api.gateway.common.handler;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import me.chanjar.weixin.mp.bean.result.WxMpUser;
import org.springframework.stereotype.Component;
import vip.carpool.api.gateway.common.builder.TextBuilder;
import java.util.Map;
/**
* @author 刘荣华
*/
@Component
public class SubscribeHandler extends AbstractHandler {
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,
Map<String, Object> context, WxMpService weixinService,
WxSessionManager sessionManager) throws WxErrorException {
this.logger.info("新关注用户 OPENID: " + wxMessage.getFromUser());
// 获取微信用户基本信息
try {
WxMpUser userWxInfo = weixinService.getUserService()
.userInfo(wxMessage.getFromUser(), null);
if (userWxInfo != null) {
// TODO 可以添加关注用户到本地数据库
}
} catch (WxErrorException e) {
if (e.getError().getErrorCode() == 48001) {
this.logger.info("该公众号没有获取用户信息权限!");
}
}
WxMpXmlOutMessage responseResult = null;
try {
responseResult = this.handleSpecial(wxMessage);
} catch (Exception e) {
this.logger.error(e.getMessage(), e);
}
if (responseResult != null) {
return responseResult;
}
try {
return new TextBuilder().build("感谢关注", wxMessage, weixinService);
} catch (Exception e) {
this.logger.error(e.getMessage(), e);
}
return null;
}
/**
* 处理特殊请求比如如果是扫码进来的可以做相应处理
*/
private WxMpXmlOutMessage handleSpecial(WxMpXmlMessage wxMessage)
throws Exception {
//TODO
return null;
}
}

View File

@ -0,0 +1,27 @@
package vip.carpool.api.gateway.common.handler;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* @author 刘荣华
*/
@Component
public class UnsubscribeHandler extends AbstractHandler {
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,
Map<String, Object> context, WxMpService wxMpService,
WxSessionManager sessionManager) {
String openId = wxMessage.getFromUser();
this.logger.info("取消关注用户 OPENID: " + openId);
// TODO 可以更新本地数据库为取消关注状态
return null;
}
}

View File

@ -0,0 +1,28 @@
package vip.carpool.api.gateway.common.utils;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
/**
* @author
*/
public class JsonUtils {
private static final ObjectMapper JSON = new ObjectMapper();
static {
JSON.setSerializationInclusion(Include.NON_NULL);
JSON.configure(SerializationFeature.INDENT_OUTPUT, Boolean.TRUE);
}
public static String toJson(Object obj) {
try {
return JSON.writeValueAsString(obj);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
}

View File

@ -0,0 +1,30 @@
package vip.carpool.api.gateway.common.utils;
import java.io.PrintWriter;
import java.io.StringWriter;
/**
* @ClassName 异常工具
* @author 刘荣华
* @Date 2019/6/27
**/
public class ThrowableUtil {
/**
* 获取堆栈信息
*
* @param throwable
* @return
*/
public static String getStackTrace(Throwable throwable) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
try {
throwable.printStackTrace(pw);
return sw.toString();
} finally {
pw.close();
}
}
}

View File

@ -0,0 +1,30 @@
package vip.carpool.api.gateway.common.utils;
import org.w3c.dom.Document;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
/**
* 2018/7/3
*/
public final class WXXmlUtil {
public static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
documentBuilderFactory.setXIncludeAware(false);
documentBuilderFactory.setExpandEntityReferences(false);
return documentBuilderFactory.newDocumentBuilder();
}
public static Document newDocument() throws ParserConfigurationException {
return newDocumentBuilder().newDocument();
}
}

View File

@ -0,0 +1,102 @@
package vip.carpool.api.gateway.common.utils;
import lombok.extern.slf4j.Slf4j;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
@Slf4j
public class XmlMapUtil {
private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final Random RANDOM = new SecureRandom();
/**
* XML格式字符串转换为Map
*
* @param strXML XML字符串
* @return XML数据转换后的Map
* @throws Exception
*/
public static Map<String, String> xmlToMap(String strXML) throws Exception {
try {
Map<String, String> data = new HashMap<String, String>();
DocumentBuilder documentBuilder = WXXmlUtil.newDocumentBuilder();
InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
Document doc = documentBuilder.parse(stream);
doc.getDocumentElement().normalize();
NodeList nodeList = doc.getDocumentElement().getChildNodes();
for (int idx = 0; idx < nodeList.getLength(); ++idx) {
Node node = nodeList.item(idx);
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
data.put(element.getNodeName(), element.getTextContent());
}
}
try {
stream.close();
} catch (Exception ex) {
// do nothing
}
return data;
} catch (Exception ex) {
log.warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
throw ex;
}
}
/**
* 将Map转换为XML格式的字符串
*
* @param data Map类型数据
* @return XML格式的字符串
* @throws Exception
*/
public static String mapToXml(Map<String, String> data) throws Exception {
Document document = WXXmlUtil.newDocument();
Element root = document.createElement("xml");
document.appendChild(root);
for (String key : data.keySet()) {
String value = data.get(key);
if (value == null) {
value = "";
}
value = value.trim();
Element filed = document.createElement(key);
filed.appendChild(document.createTextNode(value));
root.appendChild(filed);
}
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
DOMSource source = new DOMSource(document);
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
transformer.transform(source, result);
String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
try {
writer.close();
} catch (Exception ex) {
}
return output;
}
}

View File

@ -0,0 +1,24 @@
package vip.carpool.api.gateway.common.web.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import springfox.documentation.annotations.ApiIgnore;
@Controller
@RequestMapping("/docs")
@ApiIgnore
public class ApiDocController extends BaseController {
/**
* swaggerUI
*/
@GetMapping("")
public String swaggerUI(){
return "redirect:/swagger-ui.html";
}
}

View File

@ -0,0 +1,32 @@
package vip.carpool.api.gateway.common.web.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import vip.carpool.api.gateway.common.api.ApiController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
public abstract class BaseController extends ApiController {
/**
* 获取当前请求
*
* @return request
*/
public HttpServletRequest getRequest() {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
}
/**
* 获取当前请求
*
* @return response
*/
public HttpServletResponse getResponse() {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
}
}

View File

@ -0,0 +1,60 @@
package vip.carpool.api.gateway.common.web.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ApiModel("ID-NAME-VO")
public class CommonIdName {
@ApiModelProperty("id")
private String id;
@ApiModelProperty("名称")
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public CommonIdName(String id, String name) {
super();
this.id = id;
this.name = name;
}
public CommonIdName() {
super();
}
@Override
public String toString() {
return "CommonIdName [id=" + id + ", name=" + name + "]";
}
}

View File

@ -0,0 +1,90 @@
package vip.carpool.api.gateway.controller.wechat;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.bean.WxJsapiSignature;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;
import me.chanjar.weixin.mp.bean.result.WxMpUser;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import vip.carpool.api.gateway.common.api.ApiResult;
import vip.carpool.api.gateway.common.exception.BadRequestException;
import javax.annotation.Resource;
@Slf4j
@RestController
@RequestMapping("/wechat")
@Api(value = "微信公众号模块", tags = "微信公众号模块", description = "微信公众号模块")
public class WeChatController {
@Resource
private WxMpService wxMpService;
/**
* 小程序颁发token暂时模拟openid
*
* @return token字符串
*/
@PostMapping("/oauth/accessToken")
@ApiOperation(value = "获取公众号accessToken", notes = "获取公众号accessToken")
public ApiResult login(@ApiParam(name = "code必传", required = true) @RequestParam("code") String code) {
log.info("客户绑定颁发token入参{}", code);
try {
log.info("正在获取公众号受权……");
// 获取accessToken
WxMpOAuth2AccessToken accessToken = wxMpService.oauth2getAccessToken(code);
// 获取用户信息
WxMpUser wxMpUser = wxMpService.oauth2getUserInfo(accessToken, null);
log.info("正在获取公众号受权结束unionId为{}", wxMpUser.getUnionId());
return ApiResult.ok(wxMpUser);
} catch (WxErrorException e) {
log.error("获取公众号受权失败 accessToken: code={}, error={}", code, e.getMessage());
throw new BadRequestException("获取公众号受权失败");
}
}
/**
* 小程序颁发token暂时模拟openid
*
* @return token字符串
*/
@PostMapping("/oauth/getWxMpUser")
@ApiOperation(value = "获取公众号用户信息", notes = "获取公众号用户信息")
public ApiResult getWxMpUser(@ApiParam(name = "accessToken必传", required = true) @RequestParam("accessToken") String accessToken,
@ApiParam(name = "openId必传", required = true) @RequestParam("openId") String openId) {
log.info("客户绑定颁发token入参{}", accessToken);
try {
log.info("正在获取公众号用户信息……");
// 获取accessToken
WxMpOAuth2AccessToken oAuth2AccessToken = new WxMpOAuth2AccessToken();
oAuth2AccessToken.setAccessToken(accessToken);
oAuth2AccessToken.setOpenId(openId);
// 获取用户信息
WxMpUser wxMpUser = wxMpService.oauth2getUserInfo(oAuth2AccessToken, null);
log.info("正在获取公众号受权结束unionId为{}", wxMpUser.getUnionId());
return ApiResult.ok(wxMpUser);
} catch (WxErrorException e) {
log.error("获取公众号受权失败 accessToken: {}, openId={}, error={}", accessToken, openId, e.getMessage());
throw new BadRequestException("获取公众号受权失败");
}
}
@PostMapping("/getShareInfo")
@ApiOperation(value = "公众号分享", notes = "公众号分享功能")
public ApiResult share(@ApiParam(name = "需要分享的url必传", required = true) @RequestParam("url") String url) {
try {
WxJsapiSignature wxJsapiSignature = wxMpService.createJsapiSignature(url);
return ApiResult.ok(wxJsapiSignature);
} catch (WxErrorException e) {
log.error("公众号分享: url={}, error={}", url, e.getMessage());
throw new BadRequestException("公众号分享失败");
}
}
}

View File

@ -0,0 +1,111 @@
package vip.carpool.api.gateway.controller.wechat;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaKefuMessage;
import com.alibaba.fastjson.JSON;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import vip.carpool.api.gateway.common.api.ApiCode;
import vip.carpool.api.gateway.common.api.ApiResult;
import vip.carpool.api.gateway.common.exception.BadRequestException;
import vip.carpool.api.gateway.common.utils.ThrowableUtil;
import vip.carpool.api.gateway.dto.SubscribeMessageDTO;
import vip.carpool.api.gateway.vo.TemplateData;
import vip.carpool.api.gateway.vo.TemplateMsgVO;
import vip.carpool.api.gateway.vo.WxMsg;
import vip.carpool.api.gateway.vo.WxMssVo;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping
@Api(value = "微信小程序回复用户消息模块", tags = "微信小程序回复用户消息模块", description = "微信小程序回复用户消息模块")
public class WeChatMessageController {
@Resource
private WxMaService wxMiniService;
@PostMapping("/sendMessage")
public void sendMessage(String openId, String message) throws WxErrorException {
WxMaKefuMessage wxMaKefuMessage = new WxMaKefuMessage();
wxMaKefuMessage.setMsgType("text");
wxMaKefuMessage.setToUser(openId);
WxMaKefuMessage.KfText text = new WxMaKefuMessage.KfText(message);
wxMaKefuMessage.setText(text);
wxMiniService.getMsgService().sendKefuMsg(wxMaKefuMessage);
log.info("成功回复消息:" + message);
}
/*public static void main(String[] args) {
List<WxMsg> list = new ArrayList<>();
WxMsg wxMsg = new WxMsg();
wxMsg.setName("keyword1");
wxMsg.setValue("易鑫车贷");
list.add(wxMsg);
wxMsg = new WxMsg();
wxMsg.setName("keyword2");
wxMsg.setValue("尊敬的客户:您在农业银行的贷款申请已受理,具体贷款结果请咨询合作机构!");
list.add(wxMsg);
String msg = JSON.toJSONString(list);
JSONObject object = new JSONObject();
object.put("msgs", msg);
System.out.println(object.toJSONString());
}*/
@PostMapping("/sendSubscribeMessage")
public ApiResult sendSubscribeMessage(@Validated @RequestBody TemplateMsgVO templateMsgVO) {
try {
WxMssVo subscribeMessage = new WxMssVo();
//跳转小程序页面路径
subscribeMessage.setPage(templateMsgVO.getUrl());
//模板消息id
subscribeMessage.setTemplate_id(templateMsgVO.getTemplateId());
//给谁推送 用户的openid 可以调用根据code换openid接口)
subscribeMessage.setTouser(templateMsgVO.getOpenId());
//==========================================创建一个参数集合========================================================
Gson gson = new Gson();
String msgs = templateMsgVO.getMsgs();
Map<String, TemplateData> wxMaTemplateDataMap = new HashMap<>();
List<WxMsg> msgList = gson.fromJson(msgs, new TypeToken<List<WxMsg>>() {
}.getType());
if (msgs != null && msgList.size() > 0) {
for (WxMsg wxMsg : msgList) {
wxMaTemplateDataMap.put(wxMsg.getName(), new TemplateData(wxMsg.getValue()));
}
}
subscribeMessage.setData(wxMaTemplateDataMap);
String str = JSON.toJSONString(subscribeMessage);
log.info("给客户推送消息:{}", str);
//这里简单起见我们每次都获取最新的access_token时间开发中应该在access_token快过期时再重新获取
String url = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=" + wxMiniService.getAccessToken();
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, subscribeMessage, String.class);
SubscribeMessageDTO subscribeMessageDTO = JSON.parseObject(responseEntity.getBody(), SubscribeMessageDTO.class);
if(null != subscribeMessageDTO.getErrcode() && subscribeMessageDTO.getErrcode().equals(0)){
return ApiResult.ok(responseEntity.getBody());
}
return ApiResult.fail(ApiCode.FAIL, responseEntity.getBody());
} catch (WxErrorException e) {
log.error("消息推送失败:{}", ThrowableUtil.getStackTrace(e));
throw new BadRequestException("消息推送失败:" + e.getMessage());
}
}
}

View File

@ -0,0 +1,61 @@
package vip.carpool.api.gateway.controller.wechat;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import vip.carpool.api.gateway.common.api.ApiResult;
import vip.carpool.api.gateway.common.exception.BadRequestException;
import javax.annotation.Resource;
@Slf4j
@RestController
@RequestMapping("/wxmini")
@Api(value = "微信小程序模块", tags = "微信小程序模块", description = "微信小程序模块")
public class WeChatMiniController {
@Resource
private WxMaService wxMiniService;
/**
* 小程序颁发token暂时模拟openid
*
* @return token字符串
*/
@PostMapping("/oauth/accessToken")
@ApiOperation(value = "获取小程序accessToken", notes = "获取小程序accessToken")
public ApiResult login(@ApiParam(name = "code必传", required = true) @RequestParam("code") String code,
@ApiParam(name = "encryptedData必传", required = true) @RequestParam("encryptedData") String encryptedData,
@ApiParam(name = "iv必传", required = true) @RequestParam("iv") String iv) {
try {
log.info("正在获取小程序受权……");
WxMaJscode2SessionResult session = wxMiniService.getUserService().getSessionInfo(code);
log.info("正在获取小程序受权session为{}", session.toString());
// 先获取微信token
String sessionKey = session.getSessionKey();
String unionId = session.getUnionid();
// 解密用户信息
WxMaUserInfo userInfo = wxMiniService.getUserService().getUserInfo(sessionKey, encryptedData, iv);
if (StringUtils.isBlank(unionId)) {
unionId = userInfo.getUnionId();
} else {
userInfo.setUnionId(unionId);
}
log.info("正在获取小程序受权结束unionId为{}userInfo{}", unionId, userInfo.toString());
return ApiResult.ok(userInfo);
} catch (WxErrorException e) {
log.error("获取小程序受权失败 accessToken: code={}, error={}", code, e.getMessage());
throw new BadRequestException("获取小程序受权失败");
}
}
}

View File

@ -0,0 +1,160 @@
package vip.carpool.api.gateway.controller.wechat;
import cn.hutool.core.util.XmlUtil;
import com.sun.org.apache.xml.internal.serialize.OutputFormat;
import com.sun.org.apache.xml.internal.serialize.XMLSerializer;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.w3c.dom.Document;
import vip.carpool.api.gateway.common.api.ApiCode;
import vip.carpool.api.gateway.common.api.ApiResult;
import vip.carpool.api.gateway.common.exception.BadRequestException;
import vip.carpool.api.gateway.common.utils.XmlMapUtil;
import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.security.*;
import java.security.cert.CertificateException;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/wechat")
@Api(value = "企业付款到用户零钱", tags = "企业付款到用户零钱", description = "企业付款到用户零钱")
public class WeChatPayUserController {
private final static String ACTIVE_TEST = "test";
private final static String ACTIVE_PROD = "prod";
private static final String CONFIG_FILENAME_DEV = "client_cert_dev.p12";
private static final String CONFIG_FILENAME_TEST = "client_cert_test.p12";
private static final String CONFIG_FILENAME_PROD = "client_cert_prod.p12";
@Value("${spring.profiles.active}")
private String env;
@Value("${wx.client.cert.path:}")
private String clientCertPath;
@Value("${mmpaymkttransfers}")
private String mmpaymkttransfers;
@PostMapping("/payUser")
public ApiResult payUser(String xmlData, String certificateKey) {
if (StringUtils.isBlank(xmlData) || StringUtils.isBlank(certificateKey)) {
throw new BadRequestException("付款入参为空请检查xml mchId");
}
String path;
if (env.equals(ACTIVE_TEST)) {
path = clientCertPath + CONFIG_FILENAME_TEST;
} else if (env.equals(ACTIVE_PROD)) {
path = clientCertPath + CONFIG_FILENAME_PROD;
} else {
path = CONFIG_FILENAME_DEV;
}
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(path);
try {
//指定读取证书格式为PKCS12
KeyStore keyStore = KeyStore.getInstance("PKCS12");
;
//指定PKCS12的密码(商户ID)
keyStore.load(inputStream, certificateKey.toCharArray());
// Trust own CA and all self-signed certs
SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, certificateKey.toCharArray()).build();
//指定TLS版本, Allow TLSv1 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext, new String[]{"TLSv1"}, null, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
//设置httpclient的SSLSocketFactory
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
HttpPost httppost = new HttpPost(mmpaymkttransfers);
//这里要设置编码不然xml中有中文的话会提示签名失败或者展示乱码
httppost.addHeader("Content-Type", "text/xml");
StringEntity se = new StringEntity(xmlData, "UTF-8");
httppost.setEntity(se);
CloseableHttpResponse responseEntry = httpclient.execute(httppost);
Document document = XmlUtil.readXML(responseEntry.getEntity().getContent());
String result = doc2FormatString(document);
Map<String, String> resultMap = XmlMapUtil.xmlToMap(result);
String returnCode = resultMap.get("return_code");
String resultCode = resultMap.get("result_code");
if ("FAIL".equals(returnCode) || "FAIL".equals(resultCode)) {
return ApiResult.fail(ApiCode.FAIL, resultMap);
}
resultMap.remove("mch_appid");
resultMap.remove("mchid");
return ApiResult.ok(resultMap);
} catch (KeyStoreException e) {
e.printStackTrace();
throw new BadRequestException("提取证书失败,请检查密钥是否正确:" + e.getMessage());
} catch (CertificateException e) {
e.printStackTrace();
throw new BadRequestException("提取证书异常,请检查证书是否正确:" + e.getMessage());
} catch (KeyManagementException e) {
e.printStackTrace();
throw new BadRequestException("提取证书失败:" + e.getMessage());
} catch (UnrecoverableKeyException e) {
e.printStackTrace();
throw new BadRequestException("提取证书失败,证书已损坏:" + e.getMessage());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new BadRequestException("提取证书异常请检查证书是否为PKCS12格式" + e.getMessage());
} catch (IOException e) {
e.printStackTrace();
throw new BadRequestException("读取证书异常:" + e.getMessage());
} catch (Exception e) {
e.printStackTrace();
throw new BadRequestException("xml转map异常" + e.getMessage());
} finally {
try {
if (null != inputStream) {
inputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static String doc2FormatString(Document doc) {
String docString = "";
if (doc != null) {
StringWriter stringWriter = new StringWriter();
try {
OutputFormat format = new OutputFormat(doc, "UTF-8", true);
//format.setIndenting(true);//设置是否缩进默认为true
//format.setIndent(4);//设置缩进字符数
//format.setPreserveSpace(false);//设置是否保持原来的格式,默认为 false
//format.setLineWidth(500);//设置行宽度
XMLSerializer serializer = new XMLSerializer(stringWriter, format);
serializer.asDOMSerializer();
serializer.serialize(doc);
docString = stringWriter.toString();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (stringWriter != null) {
try {
stringWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
log.info("XML内容" + docString);
return docString;
}
}

View File

@ -0,0 +1,77 @@
package vip.carpool.api.gateway.controller.wechat;
import com.alibaba.fastjson.JSONObject;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.template.WxMpTemplateData;
import me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import vip.carpool.api.gateway.common.api.ApiResult;
import vip.carpool.api.gateway.common.exception.BadRequestException;
import vip.carpool.api.gateway.common.utils.ThrowableUtil;
import vip.carpool.api.gateway.vo.TemplateMsgVO;
import vip.carpool.api.gateway.vo.WxMsg;
import javax.annotation.Resource;
import java.util.List;
/**
* @Author: huangguojie
* @Date: 2020/7/19 14:54
* To change this template use File | Settings | File Templates.
* Description:
*/
@Slf4j
@RestController
@RequestMapping("/wechat")
@Api(value = "推送模版消息", tags = "推送模版消息", description = "推送模版消息")
public class WeChatTemplateMessageController {
@Resource
private WxMpService wxService;
/**
* 推送模版消息
*
* @param templateMsgVO TemplateMsgVO
* @return ApiResult
*/
@PostMapping(value = "/sendTemplateMsg")
@ApiOperation(value = "推送模版消息", notes = "推送模版消息")
public ApiResult sendTemplateMsg(@Validated @RequestBody TemplateMsgVO templateMsgVO) {
WxMpTemplateMessage templateMessage = WxMpTemplateMessage.builder()
.toUser(templateMsgVO.getOpenId())
.templateId(templateMsgVO.getTemplateId())
.url(templateMsgVO.getUrl())
.build();
try {
Gson gson = new Gson();
String msgs = templateMsgVO.getMsgs();
List<WxMsg> msgList = gson.fromJson(msgs, new TypeToken<List<WxMsg>>(){}.getType());
if(msgs != null && msgList.size() > 0) {
for(WxMsg wxMsg: msgList) {
WxMpTemplateData templateData = new WxMpTemplateData(wxMsg.getName(), wxMsg.getValue(), wxMsg.getColor());
templateMessage.addData(templateData);
}
}
log.info("给客户推送消息:{}", templateMessage.toJson());
String msgId = this.wxService.getTemplateMsgService().sendTemplateMsg(templateMessage);
log.info("给客户推送消息结果:{}", msgId);
JSONObject jsonObject = new JSONObject();
jsonObject.put("msgId", msgId);
return ApiResult.ok(jsonObject);
} catch (WxErrorException e) {
log.error("消息推送失败:{}", ThrowableUtil.getStackTrace(e));
throw new BadRequestException("消息推送失败:" + e.getMessage());
}
}
}

View File

@ -0,0 +1,21 @@
package vip.carpool.api.gateway.dto;
import lombok.Data;
/**
* @Author: huangguojie
* @Date: 2020/12/13 15:13
* To change this template use File | Settings | File Templates.
* Description:
*/
@Data
public class SubscribeMessageDTO {
/**
* errcode : 0
* errmsg : ok
*/
private Integer errcode;
private String errmsg;
}

View File

@ -0,0 +1,16 @@
package vip.carpool.api.gateway.dto;
import lombok.Data;
/**
* @Author: huangguojie
* @Date: 2020/9/19 20:25
* To change this template use File | Settings | File Templates.
* Description:
*/
@Data
public class UserOfflineTaskFinishCallDTO {
private Integer retCode;
private String retMsg;
private String content;
}

View File

@ -0,0 +1,18 @@
package vip.carpool.api.gateway.vo;
import lombok.Data;
/**
* @Author: huangguojie
* @Date: 2020/9/25 21:24
* To change this template use File | Settings | File Templates.
* Description:
*/
@Data
public class CustomerVO {
private String openId;
private String secretId;
private String secretKey;
private String projectId;
private String subProjectId;
}

View File

@ -0,0 +1,18 @@
package vip.carpool.api.gateway.vo;
import lombok.Data;
/**
* @Author: huangguojie
* @Date: 2020/12/13 11:49
* To change this template use File | Settings | File Templates.
* Description:
*/
@Data
public class TemplateData {
private String value;
public TemplateData(String value) {
this.value = value;
}
}

View File

@ -0,0 +1,30 @@
package vip.carpool.api.gateway.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* @author 刘荣华
*/
@Data
@ApiModel(value = "推送模版消息入参")
public class TemplateMsgVO {
@NotBlank(message = "必传")
@ApiModelProperty(value = "openId")
private String openId;
@NotBlank(message = "模板id必传")
@ApiModelProperty(value = "模板id")
private String templateId;
@ApiModelProperty(value = "需要跳转的url")
private String url;
@NotBlank(message = "消息体必传")
@ApiModelProperty(value = "消息体")
private String msgs;
}

View File

@ -0,0 +1,15 @@
package vip.carpool.api.gateway.vo;
import lombok.Data;
/**
* @Author: huangguojie
* @Date: 2020/9/19 20:25
* To change this template use File | Settings | File Templates.
* Description:
*/
@Data
public class UserOfflineTaskFinishVO {
private String url;
private String data;
}

View File

@ -0,0 +1,49 @@
package vip.carpool.api.gateway.vo;
import javax.validation.constraints.NotBlank;
public class WxMsg {
@NotBlank(message = "消息参数必填")
private String name;
@NotBlank(message = "消息参数值必填")
private String value;
@NotBlank(message = "参数值颜色必填")
private String color;
public WxMsg() {
}
public WxMsg(String name , String value, String color) {
this.name = name;
this.value = value;
this.color = color;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}

View File

@ -0,0 +1,19 @@
package vip.carpool.api.gateway.vo;
import lombok.Data;
import java.util.Map;
/**
* @Author: huangguojie
* @Date: 2020/12/13 11:49
* To change this template use File | Settings | File Templates.
* Description:
*/
@Data
public class WxMssVo {
private String touser;//用户openid
private String template_id;//订阅消息模版id
private String page = "pages/index/index";//默认跳到小程序首页
private Map<String, TemplateData> data;//推送文字
}

View File

@ -0,0 +1,30 @@
wx:
client:
cert:
path: config/
# 是否开启微信真实环境
is-product: true
wbank:
clientId:
apikey:
pay:
appId:
mchId:
mchKey:
subAppId:
subMchId:
keyPath:
miniapp:
appid: wxad50a166ce47e667
secret: c3f96e82e681e4f1ac8416a662a169e7
token:
aesKey:
msgDataFormat: JSON
mp:
configs:
#一个公众号的appid
- appId:
#公众号的appsecret
secret:
token: #接口配置里的Token值
aesKey: #接口配置里的EncodingAESKey值

View File

@ -0,0 +1,30 @@
wx:
client:
cert:
path: config/
# 是否开启微信真实环境
is-product: true
wbank:
clientId:
apikey:
pay:
appId:
mchId:
mchKey:
subAppId:
subMchId:
keyPath:
miniapp:
appid: wxad50a166ce47e667
secret: c3f96e82e681e4f1ac8416a662a169e7
token:
aesKey:
msgDataFormat: JSON
mp:
configs:
#一个公众号的appid
- appId:
#公众号的appsecret
secret:
token: #接口配置里的Token值
aesKey: #接口配置里的EncodingAESKey值

View File

@ -0,0 +1,30 @@
wx:
client:
cert:
path: config/
# 是否开启微信真实环境
is-product: true
wbank:
clientId:
apikey:
pay:
appId:
mchId:
mchKey:
subAppId:
subMchId:
keyPath:
miniapp:
appid:
secret:
token:
aesKey:
msgDataFormat: JSON
mp:
configs:
#一个公众号的appid
- appId:
#公众号的appsecret
secret:
token: #接口配置里的Token值
aesKey: #接口配置里的EncodingAESKey值

View File

@ -0,0 +1,31 @@
server:
port: 7085
spring:
#启用环境(开发、测试、生产)
profiles:
active: prod
swagger:
enabled: true
title: 标题
description: 描述信息
version: 1.0
contact:
name: 维护者信息
logging:
level:
com.abchina.api.gateway: DEBUG
org.springframework.web: INFO
com.github.binarywang.demo.wx.mp: DEBUG
me.chanjar.weixin: DEBUG
# 农行api地址
abchina:
# 名称
name: abchina-api-gateway
# 版本
version: 1.0.0
# 版权年份
copyrightYear: 2020
mmpaymkttransfers: https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers

View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 日志存放路径 -->
<!-- <property name="log.path" value="/home/abchina-api-gateway/logs" />-->
<property name="log.path" value="/accd/wbank/logs/carpool-ex-api/logs" />
<!-- 日志输出格式 -->
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
<!-- 控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<!-- 系统日志输出 -->
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/abchina-api-gateway.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/abchina-api-gateway.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>INFO</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/abchina-api-gateway-error.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/abchina-api-gateway-error.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>ERROR</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 系统模块日志级别控制 -->
<logger name="com.abchina" level="info" />
<!-- Spring日志级别控制 -->
<logger name="org.springframework" level="warn" />
<root level="info">
<appender-ref ref="console" />
</root>
<!--系统操作日志-->
<root level="info">
<appender-ref ref="file_info" />
<appender-ref ref="file_error" />
</root>
</configuration>

35
pom.xml
View File

@ -33,6 +33,7 @@
<poi.version>4.1.2</poi.version>
<velocity.version>1.7</velocity.version>
<jwt.version>0.9.1</jwt.version>
<hutool.version>5.2.0</hutool.version>
</properties>
<!-- 依赖声明 -->
@ -205,6 +206,37 @@
<version>${ruoyi.version}</version>
</dependency>
<!--hutool 工具包-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!-- swagger2-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger.version}</version>
<exclusions>
<exclusion>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
</exclusion>
<exclusion>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- swagger2-UI-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${swagger.version}</version>
</dependency>
<!-- 拼车小程序模块 -->
<dependency>
<groupId>com.ruoyi</groupId>
@ -213,6 +245,8 @@
</dependency>
</dependencies>
</dependencyManagement>
@ -224,6 +258,7 @@
<module>ruoyi-generator</module>
<module>ruoyi-common</module>
<module>ruiyi-carpool</module>
<module>carpool-wx-api</module>
</modules>
<packaging>pom</packaging>