commit dc534d7ee6c58e8e74102162d96535a8009c3a24 Author: ivmiku <124345843+ivmiku@users.noreply.github.com> Date: Sat Aug 3 20:30:51 2024 +0800 repo init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/ApifoxUploaderProjectSetting.xml b/.idea/ApifoxUploaderProjectSetting.xml new file mode 100644 index 0000000..67e678c --- /dev/null +++ b/.idea/ApifoxUploaderProjectSetting.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..240ee29 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..85fda15 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,66 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..fdc35ea --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/commons/pom.xml b/commons/pom.xml new file mode 100644 index 0000000..1bf709a --- /dev/null +++ b/commons/pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + + com.ivmiku.tutorial + first-tutorial + 1.0-SNAPSHOT + + + commons + + + 21 + 21 + UTF-8 + + + + + org.projectlombok + lombok + provided + + + com.alibaba.fastjson2 + fastjson2 + + + \ No newline at end of file diff --git a/commons/src/main/java/com/ivmiku/tutorial/response/Result.java b/commons/src/main/java/com/ivmiku/tutorial/response/Result.java new file mode 100644 index 0000000..479a967 --- /dev/null +++ b/commons/src/main/java/com/ivmiku/tutorial/response/Result.java @@ -0,0 +1,53 @@ +package com.ivmiku.tutorial.response; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Result implements Serializable { + @JSONField(ordinal = 1) + private int code; + @JSONField(ordinal = 2) + private String message; + @JSONField(ordinal = 3) + private Object data = null; + + public Result(ResultCode resultCode) { + this.code = resultCode.getCode(); + this.message = resultCode.getMessage(); + } + + public Result(ResultCode resultCode, Object data) { + this.code = resultCode.getCode(); + this.message = resultCode.getMessage(); + this.data = data; + } + + public static Result ok(){ + return new Result(200, "success", null); + } + + public static Result error(){ + return new Result(-1, "error", null); + } + + public static Result ok(Object data){ + return new Result(200, "success", data); + } + + public static Result ok(String message){ + return new Result(200, message, null); + } + + public static Result error(String message){ + return new Result(-1, message, null); + } + + +} diff --git a/commons/src/main/java/com/ivmiku/tutorial/response/ResultCode.java b/commons/src/main/java/com/ivmiku/tutorial/response/ResultCode.java new file mode 100644 index 0000000..3f03573 --- /dev/null +++ b/commons/src/main/java/com/ivmiku/tutorial/response/ResultCode.java @@ -0,0 +1,17 @@ +package com.ivmiku.tutorial.response; + +import lombok.Getter; + +@Getter +public enum ResultCode { + OK(200, "success"), + ERROR(-1, "error"); + + private int code; + private String message; + + ResultCode(int code, String message) { + this.code = code; + this.message = message; + } +} diff --git a/gateway-8133/pom.xml b/gateway-8133/pom.xml new file mode 100644 index 0000000..35a8bee --- /dev/null +++ b/gateway-8133/pom.xml @@ -0,0 +1,111 @@ + + + 4.0.0 + + com.ivmiku.tutorial + first-tutorial + 1.0-SNAPSHOT + + + gateway-8133 + + + 21 + 21 + UTF-8 + + + + + + org.springframework.cloud + spring-cloud-starter-gateway + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + org.springframework.boot + spring-boot-starter-actuator + + + org.projectlombok + lombok + provided + + + + cn.dev33 + sa-token-reactor-spring-boot3-starter + 1.38.0 + + + + cn.dev33 + sa-token-redis-jackson + 1.38.0 + + + org.apache.commons + commons-pool2 + + + org.springframework.cloud + spring-cloud-starter-loadbalancer + + + + io.micrometer + micrometer-tracing + + + + io.micrometer + micrometer-tracing-bridge-brave + + + + io.micrometer + micrometer-observation + + + + io.zipkin.reporter2 + zipkin-reporter-brave + + + com.alibaba.csp + sentinel-transport-simple-http + 1.8.6 + + + com.alibaba.csp + sentinel-spring-cloud-gateway-adapter + 1.8.6 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + 3.0.13 + + com.ivmiku.tutorial.Main8133 + JAR + + + + + repackage + + + + + + + \ No newline at end of file diff --git a/gateway-8133/src/main/java/com/ivmiku/tutorial/Main8133.java b/gateway-8133/src/main/java/com/ivmiku/tutorial/Main8133.java new file mode 100644 index 0000000..954215d --- /dev/null +++ b/gateway-8133/src/main/java/com/ivmiku/tutorial/Main8133.java @@ -0,0 +1,11 @@ +package com.ivmiku.tutorial; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Main8133 { + public static void main(String[] args) { + SpringApplication.run(Main8133.class, args); + } +} \ No newline at end of file diff --git a/gateway-8133/src/main/java/com/ivmiku/tutorial/component/ForwardAuthFilter.java b/gateway-8133/src/main/java/com/ivmiku/tutorial/component/ForwardAuthFilter.java new file mode 100644 index 0000000..affa686 --- /dev/null +++ b/gateway-8133/src/main/java/com/ivmiku/tutorial/component/ForwardAuthFilter.java @@ -0,0 +1,28 @@ +package com.ivmiku.tutorial.component; + +import cn.dev33.satoken.same.SaSameUtil; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +/** + * 全局过滤器,为请求添加 Same-Token + */ +@Component +public class ForwardAuthFilter implements GlobalFilter { + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + ServerHttpRequest newRequest = exchange + .getRequest() + .mutate() + // 为请求追加 Same-Token 参数 + .header(SaSameUtil.SAME_TOKEN, SaSameUtil.getToken()) + .build(); + ServerWebExchange newExchange = exchange.mutate().request(newRequest).build(); + return chain.filter(newExchange); + } +} + diff --git a/gateway-8133/src/main/java/com/ivmiku/tutorial/component/StpInterfaceImpl.java b/gateway-8133/src/main/java/com/ivmiku/tutorial/component/StpInterfaceImpl.java new file mode 100644 index 0000000..aa3adaf --- /dev/null +++ b/gateway-8133/src/main/java/com/ivmiku/tutorial/component/StpInterfaceImpl.java @@ -0,0 +1,27 @@ +package com.ivmiku.tutorial.component; + +import cn.dev33.satoken.stp.StpInterface; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 自定义权限验证接口扩展 + */ +@Component +public class StpInterfaceImpl implements StpInterface { + + @Override + public List getPermissionList(Object loginId, String loginType) { + // 返回此 loginId 拥有的权限列表 + return null; + } + + @Override + public List getRoleList(Object loginId, String loginType) { + // 返回此 loginId 拥有的角色列表 + return null; + } + +} + diff --git a/gateway-8133/src/main/java/com/ivmiku/tutorial/config/SaTokenConfigure.java b/gateway-8133/src/main/java/com/ivmiku/tutorial/config/SaTokenConfigure.java new file mode 100644 index 0000000..59bfbdc --- /dev/null +++ b/gateway-8133/src/main/java/com/ivmiku/tutorial/config/SaTokenConfigure.java @@ -0,0 +1,36 @@ +package com.ivmiku.tutorial.config; + +import cn.dev33.satoken.reactor.filter.SaReactorFilter; +import cn.dev33.satoken.router.SaRouter; +import cn.dev33.satoken.stp.StpUtil; +import cn.dev33.satoken.util.SaResult; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * [Sa-Token 权限认证] 配置类 + * @author click33 + */ +@Configuration +public class SaTokenConfigure { + // 注册 Sa-Token全局过滤器 + @Bean + public SaReactorFilter getSaReactorFilter() { + return new SaReactorFilter() + // 拦截地址 + .addInclude("/**") /* 拦截全部path */ + // 开放地址 + .addExclude("/favicon.ico") + // 鉴权方法:每次访问进入 + .setAuth(obj -> { + // 登录校验 -- 拦截所有路由,并排除/user/doLogin 用于开放登录 + SaRouter.match("/**", "/user/**", r -> StpUtil.checkLogin()); + }) + // 异常处理方法:每次setAuth函数出现异常时进入 + .setError(e -> { + return SaResult.error(e.getMessage()); + }) + ; + } +} + diff --git a/gateway-8133/src/main/java/com/ivmiku/tutorial/config/SentinelConfig.java b/gateway-8133/src/main/java/com/ivmiku/tutorial/config/SentinelConfig.java new file mode 100644 index 0000000..62aaecc --- /dev/null +++ b/gateway-8133/src/main/java/com/ivmiku/tutorial/config/SentinelConfig.java @@ -0,0 +1,80 @@ +package com.ivmiku.tutorial.config; + +import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; +import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule; +import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayParamFlowItem; +import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager; +import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter; +import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler; +import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager; +import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler; +import jakarta.annotation.PostConstruct; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.codec.ServerCodecConfigurer; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.server.ServerResponse; +import org.springframework.web.reactive.result.view.ViewResolver; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +import java.util.*; + +@Configuration +public class SentinelConfig { + private final List viewResolvers; + private final ServerCodecConfigurer serverCodecConfigurer; + + public SentinelConfig(ObjectProvider> viewResolversProvider, + ServerCodecConfigurer serverCodecConfigurer) { + this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList); + this.serverCodecConfigurer = serverCodecConfigurer; + } + + @Bean + @Order(Ordered.HIGHEST_PRECEDENCE) + public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { + // Register the block exception handler for Spring Cloud Gateway. + return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer); + } + + @Bean + @Order(-1) + public GlobalFilter sentinelGatewayFilter() { + return new SentinelGatewayFilter(); + } + + @PostConstruct + public void doInit() { + Set rules = new HashSet<>(); + rules.add(new GatewayFlowRule("user") + .setCount(1) + .setIntervalSec(1) + ); + rules.add(new GatewayFlowRule("user") + .setCount(2) + .setIntervalSec(2) + .setBurst(2) + .setParamItem(new GatewayParamFlowItem() + .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_CLIENT_IP) + ) + ); + GatewayRuleManager.loadRules(rules); + BlockRequestHandler handler = new BlockRequestHandler() { + @Override + public Mono handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) { + Map map = new HashMap<>(); + map.put("code", -1); + map.put("message", "系统繁忙,请稍后再试!"); + return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS).contentType(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(map)); + } + }; + GatewayCallbackManager.setBlockHandler(handler); + } +} diff --git a/gateway-8133/src/main/resources/application-dep.properties b/gateway-8133/src/main/resources/application-dep.properties new file mode 100644 index 0000000..55f6029 --- /dev/null +++ b/gateway-8133/src/main/resources/application-dep.properties @@ -0,0 +1,16 @@ +server.port=8133 + +spring.application.name=gateway +spring.cloud.nacos.discovery.server-addr=nacos:8848 + +spring.data.redis.host=redis +spring.data.redis.port=6379 +spring.data.redis.database=0 +spring.data.redis.password=Shuodedaoli114514 + +spring.cloud.gateway.routes[0].id=user +spring.cloud.gateway.routes[0].uri=lb://user +spring.cloud.gateway.routes[0].predicates[0]=Path=/user/** + +management.zipkin.tracing.endpoint=http://zipkin:9411/api/v2/spans +management.tracing.sampling.probability=1.0 \ No newline at end of file diff --git a/gateway-8133/src/main/resources/application-dev.properties b/gateway-8133/src/main/resources/application-dev.properties new file mode 100644 index 0000000..12528ab --- /dev/null +++ b/gateway-8133/src/main/resources/application-dev.properties @@ -0,0 +1,15 @@ +server.port=8133 + +spring.application.name=gateway +spring.cloud.nacos.discovery.server-addr=localhost:8848 + +spring.data.redis.host=localhost +spring.data.redis.port=6379 +spring.data.redis.database=0 + +spring.cloud.gateway.routes[0].id=user +spring.cloud.gateway.routes[0].uri=lb://user +spring.cloud.gateway.routes[0].predicates[0]=Path=/user/** + +management.zipkin.tracing.endpoint=http://localhost:9411/api/v2/spans +management.tracing.sampling.probability=1.0 \ No newline at end of file diff --git a/gateway-8133/src/main/resources/application.properties b/gateway-8133/src/main/resources/application.properties new file mode 100644 index 0000000..0896b37 --- /dev/null +++ b/gateway-8133/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.profiles.active=dep \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..8db85ab --- /dev/null +++ b/pom.xml @@ -0,0 +1,189 @@ + + + 4.0.0 + + com.ivmiku.tutorial + first-tutorial + 1.0-SNAPSHOT + pom + + gateway-8133 + user-8072 + commons + + + + 21 + 21 + UTF-8 + 21 + 21 + UTF-8 + 5.8.22 + 1.18.30 + 3.5.6 + 8.0.11 + 4.2.3 + 2.0.40 + 1.0.2 + 3.1.5 + + + + + + + 3.1.7 + 2022.0.4 + + 2022.0.0.0-RC2 + 1.2.0 + 1.12.0 + 12.5 + 2.17.0 + 1.38.0 + 3.2.13 + 4.6.0 + + + + + + + org.springframework.boot + spring-boot-starter-parent + ${spring.boot.version} + pom + import + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring.cloud.version} + pom + import + + + + com.alibaba.cloud + spring-cloud-alibaba-dependencies + ${spring.cloud.alibaba.version} + pom + import + + + + com.baomidou + mybatis-plus-spring-boot3-starter + ${mybatis.springboot.version} + + + + mysql + mysql-connector-java + ${mysql.version} + + + + javax.persistence + persistence-api + ${persistence-api.version} + + + + com.alibaba.fastjson2 + fastjson2 + ${fastjson2.version} + + + + cn.hutool + hutool-all + ${hutool.version} + + + + org.projectlombok + lombok + ${lombok.version} + true + + + + org.springframework.boot + spring-boot-starter-test + ${spring.boot.test.version} + test + + + + + io.micrometer + micrometer-tracing-bom + ${micrometer-tracing.version} + pom + import + + + + io.micrometer + micrometer-tracing + ${micrometer-tracing.version} + + + + io.micrometer + micrometer-tracing-bridge-brave + ${micrometer-tracing.version} + + + + io.micrometer + micrometer-observation + ${micrometer-observation.version} + + + + io.github.openfeign + feign-micrometer + ${feign-micrometer.version} + + + + io.zipkin.reporter2 + zipkin-reporter-brave + ${zipkin-reporter-brave.version} + + + + cn.dev33 + sa-token-spring-boot3-starter + ${satoken-version} + + + + org.apache.dubbo + dubbo-registry-nacos + ${dubbo.version} + + + + org.apache.dubbo + dubbo-bom + ${dubbo.version} + pom + import + + + com.github.binarywang + weixin-java-miniapp + ${wx.version} + + + + \ No newline at end of file diff --git a/user-8072/pom.xml b/user-8072/pom.xml new file mode 100644 index 0000000..578e8f0 --- /dev/null +++ b/user-8072/pom.xml @@ -0,0 +1,141 @@ + + + 4.0.0 + + com.ivmiku.tutorial + first-tutorial + 1.0-SNAPSHOT + + + user-8072 + + + 21 + 21 + UTF-8 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + com.baomidou + mybatis-plus-spring-boot3-starter + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + com.mysql + mysql-connector-j + runtime + + + org.projectlombok + lombok + provided + + + + cn.dev33 + sa-token-spring-boot3-starter + + + + com.alibaba.fastjson2 + fastjson2 + + + + cn.dev33 + sa-token-redis-jackson + 1.38.0 + + + org.apache.commons + commons-pool2 + + + + org.apache.dubbo + dubbo-spring-boot-starter + + + + org.apache.dubbo + dubbo-registry-nacos + + + org.springframework.cloud + spring-cloud-starter-loadbalancer + + + + io.micrometer + micrometer-tracing + + + + io.micrometer + micrometer-tracing-bridge-brave + + + + io.micrometer + micrometer-observation + + + + io.zipkin.reporter2 + zipkin-reporter-brave + + + org.apache.dubbo + dubbo-spring-boot-tracing-brave-zipkin-starter + 3.2.13 + + + com.alibaba.cloud + spring-cloud-starter-alibaba-sentinel + + + com.github.binarywang + weixin-java-miniapp + + + com.ivmiku.tutorial + commons + 1.0-SNAPSHOT + + + + + + + org.springframework.boot + spring-boot-maven-plugin + 3.0.13 + + com.ivmiku.tutorial.Main8072 + JAR + + + + + repackage + + + + + + + \ No newline at end of file diff --git a/user-8072/src/main/java/com/ivmiku/tutorial/Main8072.java b/user-8072/src/main/java/com/ivmiku/tutorial/Main8072.java new file mode 100644 index 0000000..31a15fe --- /dev/null +++ b/user-8072/src/main/java/com/ivmiku/tutorial/Main8072.java @@ -0,0 +1,13 @@ +package com.ivmiku.tutorial; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +@MapperScan("com.ivmiku.tutorial.mapper") +public class Main8072 { + public static void main(String[] args) { + SpringApplication.run(Main8072.class, args); + } +} \ No newline at end of file diff --git a/user-8072/src/main/java/com/ivmiku/tutorial/config/RedisConfig.java b/user-8072/src/main/java/com/ivmiku/tutorial/config/RedisConfig.java new file mode 100644 index 0000000..a1348ca --- /dev/null +++ b/user-8072/src/main/java/com/ivmiku/tutorial/config/RedisConfig.java @@ -0,0 +1,30 @@ +package com.ivmiku.tutorial.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.RedisSerializer; + +@Configuration +public class RedisConfig { + + @Bean + public RedisTemplate redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(lettuceConnectionFactory); + // 设置key序列化方式string,RedisSerializer.string() 等价于 new StringRedisSerializer() + redisTemplate.setKeySerializer(RedisSerializer.string()); + // 设置value的序列化方式json,使用GenericJackson2JsonRedisSerializer替换默认序列化,RedisSerializer.json() 等价于 new GenericJackson2JsonRedisSerializer() + redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class)); + // 设置hash的key的序列化方式 + redisTemplate.setHashKeySerializer(RedisSerializer.string()); + // 设置hash的value的序列化方式 + redisTemplate.setHashValueSerializer(RedisSerializer.json()); + // 使配置生效 + redisTemplate.afterPropertiesSet(); + return redisTemplate; + } + +} diff --git a/user-8072/src/main/java/com/ivmiku/tutorial/config/WxMaConfiguration.java b/user-8072/src/main/java/com/ivmiku/tutorial/config/WxMaConfiguration.java new file mode 100644 index 0000000..1fec235 --- /dev/null +++ b/user-8072/src/main/java/com/ivmiku/tutorial/config/WxMaConfiguration.java @@ -0,0 +1,128 @@ +package com.ivmiku.tutorial.config; + +import cn.binarywang.wx.miniapp.api.WxMaService; +import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl; +import cn.binarywang.wx.miniapp.bean.WxMaKefuMessage; +import cn.binarywang.wx.miniapp.bean.WxMaSubscribeMessage; +import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl; +import cn.binarywang.wx.miniapp.message.WxMaMessageHandler; +import cn.binarywang.wx.miniapp.message.WxMaMessageRouter; +import com.alibaba.nacos.shaded.com.google.common.collect.Lists; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.error.WxRuntimeException; +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 java.io.File; +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +@Configuration +@EnableConfigurationProperties(WxMaProperties.class) +public class WxMaConfiguration { + private final WxMaProperties properties; + + @Autowired + public WxMaConfiguration(WxMaProperties properties) { + this.properties = properties; + } + + @Bean + public WxMaService wxMaService() { + List configs = this.properties.getConfigs(); + if (configs == null) { + throw new WxRuntimeException("大哥,拜托先看下项目首页的说明(readme文件),添加下相关配置,注意别配错了!"); + } + WxMaService maService = new WxMaServiceImpl(); + maService.setMultiConfigs( + configs.stream() + .map(a -> { + WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl(); +// WxMaDefaultConfigImpl config = new WxMaRedisConfigImpl(new JedisPool()); + // 使用上面的配置时,需要同时引入jedis-lock的依赖,否则会报类无法找到的异常 + config.setAppid(a.getAppid()); + config.setSecret(a.getSecret()); + config.setToken(a.getToken()); + config.setAesKey(a.getAesKey()); + config.setMsgDataFormat(a.getMsgDataFormat()); + return config; + }).collect(Collectors.toMap(WxMaDefaultConfigImpl::getAppid, a -> a, (o, n) -> o))); + return maService; + } + + @Bean + public WxMaMessageRouter wxMaMessageRouter(WxMaService wxMaService) { + final WxMaMessageRouter router = new WxMaMessageRouter(wxMaService); + router + .rule().handler(logHandler).next() + .rule().async(false).content("订阅消息").handler(subscribeMsgHandler).end() + .rule().async(false).content("文本").handler(textHandler).end() + .rule().async(false).content("图片").handler(picHandler).end() + .rule().async(false).content("二维码").handler(qrcodeHandler).end(); + return router; + } + + private final WxMaMessageHandler subscribeMsgHandler = (wxMessage, context, service, sessionManager) -> { + service.getMsgService().sendSubscribeMsg(WxMaSubscribeMessage.builder() + .templateId("此处更换为自己的模板id") + .data(Lists.newArrayList( + new WxMaSubscribeMessage.MsgData("keyword1", "339208499"))) + .toUser(wxMessage.getFromUser()) + .build()); + return null; + }; + + private final WxMaMessageHandler logHandler = (wxMessage, context, service, sessionManager) -> { + log.info("收到消息:" + wxMessage.toString()); + service.getMsgService().sendKefuMsg(WxMaKefuMessage.newTextBuilder().content("收到信息为:" + wxMessage.toJson()) + .toUser(wxMessage.getFromUser()).build()); + return null; + }; + + private final WxMaMessageHandler textHandler = (wxMessage, context, service, sessionManager) -> { + service.getMsgService().sendKefuMsg(WxMaKefuMessage.newTextBuilder().content("回复文本消息") + .toUser(wxMessage.getFromUser()).build()); + return null; + }; + + private final WxMaMessageHandler picHandler = (wxMessage, context, service, sessionManager) -> { + try { + WxMediaUploadResult uploadResult = service.getMediaService() + .uploadMedia("image", "png", + ClassLoader.getSystemResourceAsStream("tmp.png")); + service.getMsgService().sendKefuMsg( + WxMaKefuMessage + .newImageBuilder() + .mediaId(uploadResult.getMediaId()) + .toUser(wxMessage.getFromUser()) + .build()); + } catch (WxErrorException e) { + e.printStackTrace(); + } + + return null; + }; + + private final WxMaMessageHandler qrcodeHandler = (wxMessage, context, service, sessionManager) -> { + try { + final File file = service.getQrcodeService().createQrcode("123", 430); + WxMediaUploadResult uploadResult = service.getMediaService().uploadMedia("image", file); + service.getMsgService().sendKefuMsg( + WxMaKefuMessage + .newImageBuilder() + .mediaId(uploadResult.getMediaId()) + .toUser(wxMessage.getFromUser()) + .build()); + } catch (WxErrorException e) { + e.printStackTrace(); + } + + return null; + }; + +} diff --git a/user-8072/src/main/java/com/ivmiku/tutorial/config/WxMaProperties.java b/user-8072/src/main/java/com/ivmiku/tutorial/config/WxMaProperties.java new file mode 100644 index 0000000..8f0d290 --- /dev/null +++ b/user-8072/src/main/java/com/ivmiku/tutorial/config/WxMaProperties.java @@ -0,0 +1,42 @@ +package com.ivmiku.tutorial.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.List; + +@Data +@ConfigurationProperties(prefix = "wx.miniapp") +public class WxMaProperties { + + private List configs; + + @Data + public static class Config { + /** + * 设置微信小程序的appid + */ + private String appid; + + /** + * 设置微信小程序的Secret + */ + private String secret; + + /** + * 设置微信小程序消息服务器配置的token + */ + private String token; + + /** + * 设置微信小程序消息服务器配置的EncodingAESKey + */ + private String aesKey; + + /** + * 消息格式,XML或者JSON + */ + private String msgDataFormat; + } + +} diff --git a/user-8072/src/main/java/com/ivmiku/tutorial/controller/UserController.java b/user-8072/src/main/java/com/ivmiku/tutorial/controller/UserController.java new file mode 100644 index 0000000..f9aedfd --- /dev/null +++ b/user-8072/src/main/java/com/ivmiku/tutorial/controller/UserController.java @@ -0,0 +1,59 @@ +package com.ivmiku.tutorial.controller; + +import cn.binarywang.wx.miniapp.api.WxMaService; +import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; +import cn.binarywang.wx.miniapp.util.WxMaConfigHolder; +import cn.dev33.satoken.stp.StpUtil; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson2.JSON; +import com.ivmiku.tutorial.entity.User; +import com.ivmiku.tutorial.mapper.UserMapper; +import com.ivmiku.tutorial.response.Result; +import com.ivmiku.tutorial.utils.RedisUtil; +import jakarta.annotation.Resource; +import me.chanjar.weixin.common.error.WxErrorException; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; + +@RestController +@RequestMapping("/user") +public class UserController { + @Resource + private WxMaService wxMaService; + + @Resource + private UserMapper userMapper; + + @Resource + private RedisUtil redisUtil; + + @GetMapping("/login") + public Object login(@RequestParam(name = "code") String code, @RequestParam(name = "rawdata") String rawData, @RequestParam(name = "signature") String signature +) { + WxMaJscode2SessionResult session; + try { + session = wxMaService.getUserService().getSessionInfo(code); + } catch (WxErrorException e) { + return JSON.toJSON(Result.error(e.getMessage())); + } + StpUtil.login(session.getOpenid()); + HashMap map = new HashMap<>(); + if (userMapper.selectById(session.getOpenid()) == null) { + if (!wxMaService.getUserService().checkUserInfo(session.getSessionKey(), rawData, signature)) { + WxMaConfigHolder.remove();//清理ThreadLocal + return JSON.toJSON(Result.error("用户数据验证错误!")); + } + JSONObject raw = JSONObject.parseObject(rawData); + User user = new User(); + user.setOpenid(session.getOpenid()); + user.setNickname(raw.getString("nickname")); + user.setAvatarUrl(raw.getString("avatarUrl")); + userMapper.insert(user); + } + map.put("token", StpUtil.getTokenValue()); + redisUtil.insertKey(session.getOpenid(), session.getSessionKey()); + WxMaConfigHolder.remove(); + return JSON.toJSON(Result.ok(map)); + } +} diff --git a/user-8072/src/main/java/com/ivmiku/tutorial/entity/User.java b/user-8072/src/main/java/com/ivmiku/tutorial/entity/User.java new file mode 100644 index 0000000..35dbcc2 --- /dev/null +++ b/user-8072/src/main/java/com/ivmiku/tutorial/entity/User.java @@ -0,0 +1,14 @@ +package com.ivmiku.tutorial.entity; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +@TableName("user") +@Data +public class User { + @TableId + private String openid; + private String nickname; + private String avatarUrl; +} diff --git a/user-8072/src/main/java/com/ivmiku/tutorial/mapper/UserMapper.java b/user-8072/src/main/java/com/ivmiku/tutorial/mapper/UserMapper.java new file mode 100644 index 0000000..f220728 --- /dev/null +++ b/user-8072/src/main/java/com/ivmiku/tutorial/mapper/UserMapper.java @@ -0,0 +1,7 @@ +package com.ivmiku.tutorial.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ivmiku.tutorial.entity.User; + +public interface UserMapper extends BaseMapper { +} diff --git a/user-8072/src/main/java/com/ivmiku/tutorial/utils/GlobalExceptionHandler.java b/user-8072/src/main/java/com/ivmiku/tutorial/utils/GlobalExceptionHandler.java new file mode 100644 index 0000000..bc0f099 --- /dev/null +++ b/user-8072/src/main/java/com/ivmiku/tutorial/utils/GlobalExceptionHandler.java @@ -0,0 +1,61 @@ +package com.ivmiku.tutorial.utils; + +import cn.dev33.satoken.exception.SaTokenException; +import com.alibaba.csp.sentinel.slots.block.BlockException; +import com.alibaba.fastjson2.JSON; +import com.ivmiku.tutorial.response.Result; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +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 java.sql.SQLException; +import java.text.ParseException; + +/** + * @author Aurora + */ +@Slf4j +@ControllerAdvice +public class GlobalExceptionHandler { + @ExceptionHandler(value = SaTokenException.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + @ResponseBody + public Object SaTokenExceptionHandler(SaTokenException e) { + log.error("SaTokenException:" + e.getLocalizedMessage()); + return JSON.toJSON(Result.error("身份认证错误:" + e.getMessage())); + } + + @ExceptionHandler(value = ParseException.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + @ResponseBody + public Object parseExceptionHandler(ParseException e) { + + log.error("ParseException:" + e.getLocalizedMessage()); + return JSON.toJSON(Result.error("请检查日期是否合法")); + } + + @ExceptionHandler(value = SQLException.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + @ResponseBody + public Object SQLExceptionHandler(SQLException e) { + log.error("SQLException:" + e.getLocalizedMessage()); + return JSON.toJSON(Result.error("数据库出错!(内部错误)\n" + e.getMessage())); + } + + @ExceptionHandler(value = BlockException.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + @ResponseBody + public Object sentinelHandler(BlockException e) { + return JSON.toJSON(Result.error("系统繁忙,请稍后再试")); + } + + @ExceptionHandler(value = Exception.class) + @ResponseBody + public Object exceptionHandler(Exception e) { + log.error("Exception:" + e.getLocalizedMessage()); + return JSON.toJSON(Result.error("服务器内部错误:" + e.getClass() + ":" + e.getMessage())); + } +} diff --git a/user-8072/src/main/java/com/ivmiku/tutorial/utils/RedisUtil.java b/user-8072/src/main/java/com/ivmiku/tutorial/utils/RedisUtil.java new file mode 100644 index 0000000..8ff2877 --- /dev/null +++ b/user-8072/src/main/java/com/ivmiku/tutorial/utils/RedisUtil.java @@ -0,0 +1,22 @@ +package com.ivmiku.tutorial.utils; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +/** + * @author Aurora + */ +@Component +public class RedisUtil { + @Autowired + private RedisTemplate redisTemplate; + + public void insertKey(String userId, String sessionKey) { + redisTemplate.opsForValue().set("sessionkey:" + userId, sessionKey); + } + + public String getKey(String userId) { + return (String) redisTemplate.opsForValue().get("sessionkey:" + userId); + } +} diff --git a/user-8072/src/main/resources/application-dep.properties b/user-8072/src/main/resources/application-dep.properties new file mode 100644 index 0000000..2e28acd --- /dev/null +++ b/user-8072/src/main/resources/application-dep.properties @@ -0,0 +1,21 @@ +wx.miniapp.configs[0].appid=wx0d4fdb5c7bf3b12b +wx.miniapp.configs[0].secret=989f155fcc3aee616568473faf1b1d3b + +spring.data.redis.host=redis +spring.data.redis.port=6379 +spring.data.redis.database=0 + +spring.application.name=user + +server.port=8072 + +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.datasource.username=root +spring.datasource.password=Shuodedaoli114514 +spring.datasource.url=jdbc:mysql://mysql:4514/tutorial?useUnicode=true&characterEncoding=utf8&useSSL=false&ServerTimezone=Asia/Shanghai + +spring.cloud.nacos.discovery.server-addr=nacos:8848 +spring.cloud.nacos.discovery.enabled=true + +management.zipkin.tracing.endpoint=http://zipkin:9411/api/v2/spans +management.tracing.sampling.probability=1.0 \ No newline at end of file diff --git a/user-8072/src/main/resources/application-dev.properties b/user-8072/src/main/resources/application-dev.properties new file mode 100644 index 0000000..bf4864f --- /dev/null +++ b/user-8072/src/main/resources/application-dev.properties @@ -0,0 +1,15 @@ +wx.miniapp.configs[0].appid=wx0d4fdb5c7bf3b12b +wx.miniapp.configs[0].secret=989f155fcc3aee616568473faf1b1d3b + +spring.data.redis.host=localhost +spring.data.redis.port=6379 +spring.data.redis.database=0 + +spring.application.name=user + +server.port=8072 + +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.datasource.username=root +spring.datasource.password=12345abcde +spring.datasource.url=jdbc:mysql://127.0.0.1:3306/tutorial?useUnicode=true&characterEncoding=utf8&useSSL=false&ServerTimezone=Asia/Shanghai diff --git a/user-8072/src/main/resources/application.properties b/user-8072/src/main/resources/application.properties new file mode 100644 index 0000000..0896b37 --- /dev/null +++ b/user-8072/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.profiles.active=dep \ No newline at end of file