1.微服务模块
将工程拆分为这几个模块:公共模块、对外API模块、管理和监控模块、业务模块等。
-
例如公共模块,在开发过程中例如防止XSS攻击的Filter、自定义校验器、异常拦截器、分页工具类、查询参数等常用的。
具体内容可以参照renren-false中的相关代码进行查看,以下用sql过滤和自定义校验工具进行举例。
sql过滤
/** * SQL过滤 * @author walid */ public class SQLFilter { /** * SQL注入过滤 * @param str 待验证的字符串 */ public static String sqlInject(String str){ if(StringUtils.isBlank(str)){ return null; } //去掉'|"|;|\字符 str = StringUtils.replace(str, "'", ""); str = StringUtils.replace(str, "\"", ""); str = StringUtils.replace(str, ";", ""); str = StringUtils.replace(str, "\\", ""); //转换成小写 str = str.toLowerCase(); //非法字符 String[] keywords = {"master", "truncate", "insert", "select", "delete", "update", "declare", "alter", "drop"}; //判断是否包含非法字符 for(String keyword : keywords){ if(str.indexOf(keyword) != -1){ throw new RRException("包含非法字符"); } } return str; } }
自定义数据校验
/**
* @description: 自定义校验器
* @program: youkeMall
* @author: walid
* @create: 2021-04-10 22:25
*/
public class ListValueConstraintValidator implements ConstraintValidator<ListValue, Integer> {
private Set<Integer> set = new HashSet<>();
/**
* @description: 初始化
* @author: walid
* @param constraintAnnotation:
* @return: void
* @time: 2021/4/10 22:27
*/
@Override
public void initialize(ListValue constraintAnnotation) {
int[] vals = constraintAnnotation.vals();
for (int v : vals ) {
set.add(v);
}
}
/**
* @description: 校验判断
* @author: walid
* @param value:
* @param context:
* @return: boolean
* @time: 2021/4/10 22:28
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);
}
}
/**
指定的校验及规则
**/
@Documented
@Constraint(validatedBy = { ListValueConstraintValidator.class })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
String message() default "{com.walid.common.valid.ListValue.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
int[] vals () default { };
}
# 配置文件ValidationMessages.properties
com.walid.common.valid.ListValue.message = 必须填写指定的值
# 自定义校验器的使用
/**
* 显示状态[0-不显示;1-显示]
*/
//自定义的校验器
@ListValue(vals = {0,1},groups = {AddValidGroup.class})
private Integer showStatus;
2. 注册中心和配置中心(nacos)
服务注册与配置中心(用于动态管理配置文件)
pom.xml(父模块中)
<!-- 服务注册/发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 配置中心来做配置管理-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
nacos作为配置中心
# bootstrap.properties
# nacos服务地址
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
# nacos配置中心命名空间ID
spring.cloud.nacos.config.namespace=4a3fb886-b332-48bb-9675-ac302ac19145
# 配置分组
spring.cloud.nacos.config.group=dev
# 指定配置中心的第一个配置
spring.cloud.nacos.config.extension-configs[0].data-id=datasource.yml
# datasouce.yml的分组
spring.cloud.nacos.config.extension-configs[0].group=dev
# 是否动态刷新配置中心
spring.cloud.nacos.config.extension-configs[0].refresh=true
nacos作为服务注册中心
# bootstrap.properties
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
开启注册和发现 注解@EnableDiscoveryClient
/**
* @description:
* @author: walid
* @param null:
* @return: null
* @time: 2021/3/8 22:31
* EnableDiscoveryClient 注解将服务注册到nacos服务中心
*/
@EnableDiscoveryClient
@SpringBootApplication
public class RenrenApplication {
public static void main(String[] args) {
SpringApplication.run(RenrenApplication.class, args);
}
}
3. 远程调用(feign)
采用
利用openfeign进行调用 pom.xml (父模块)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
声明一个远程调用服务名称
@FeignClient("walid-product")
public interface ProductFeignService {
/**
* /product/skuinfo/info/{skuId} //远程调用的地址
*
* 1)、让所有请求过网关;
* 1、@FeignClient("walid-gateway"):给walid-gateway所在的机器发请求
* 2、/api/product/skuinfo/info/{skuId}
* 2)、直接让后台指定服务处理
* 1、@FeignClient("walid-gateway")
* 2、/product/skuinfo/info/{skuId}
*
* @return
*/
@RequestMapping("/product/skuinfo/info/{skuId}")
public R info(@PathVariable("skuId") Long skuId);
}
在需要调用的服务上添加注解
@EnableFeignClients(basePackages = "com.atguigu.gulimall.product.feign")
@EnableDiscoveryClient
@MapperScan("com.atguigu.gulimall.product.dao")
@SpringBootApplication
public class GulimallProductApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallProductApplication.class, args);
}
}
远程调用
# 注入
@Autowired
ProductFeignService productFeignService;
# 调用
R r1 = couponFeignService.saveSkuReduction(skuReductionTo);
if(r1.getCode() != 0){
log.error("远程保存sku信息失败");
}
4. 网关gateway
用网关来保证前段的接口规范
pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
application.yml
spring:
cloud:
gateway:
routes:
# - id: test_route
# uri: https://www.baidu.com
# predicates:
# - Query=url,baidu
#
# - id: qq_route
# uri: https://www.qq.com
# predicates:
# - Query=url,qq
- id: product_route
uri: lb://gulimall-product
predicates:
- Path=/api/product/**
filters:
- RewritePath=/api/(?<segment>.*),/$\{segment}
- id: third_party_route
uri: lb://gulimall-third-party
predicates:
- Path=/api/thirdparty/**
filters:
- RewritePath=/api/thirdparty/(?<segment>.*),/$\{segment}
- id: member_route
uri: lb://gulimall-member
predicates:
- Path=/api/member/**
filters:
- RewritePath=/api/(?<segment>.*),/$\{segment}
- id: ware_route
uri: lb://gulimall-ware
predicates:
- Path=/api/ware/**
filters:
- RewritePath=/api/(?<segment>.*),/$\{segment}
- id: admin_route
uri: lb://renren-fast
predicates:
- Path=/api/**
filters:
- RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}
## 前端项目,/api
## 验证码
## http://localhost:88/api/captcha.jpg http://localhost:8080/renren-fast/captcha.jpg
## http://localhost:88/api/product/category/list/tree
## http://localhost:10000/product/category/list/tree
开启网关的服务注册与发现
/**
* 1、开启服务注册发现
* (配置nacos的注册中心地址)
* 2、编写网关配置文件
*/
@EnableDiscoveryClient
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class GulimallGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallGatewayApplication.class, args);
}
}
配置跨域-- 规则后面延伸
@Configuration
public class GulimallCorsConfiguration {
@Bean
public CorsWebFilter corsWebFilter(){
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
//1、配置跨域
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.setAllowCredentials(true);
source.registerCorsConfiguration("/**",corsConfiguration);
return new CorsWebFilter(source);
}
}
5. mybatis-plus插件的使用
pom.xml(公共模块)
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.18</version>
</dependency>
<!-- 导入mysql驱动 -->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.17</version>
</dependency>
配置相关的数据源信息 application.yml
# 数据库连接信息
spring:
datasource:
username:
password:
url: jdbc:mysql://XXX.XXX.XX.XX:3306/[库名]
driver-class-name: com.mysql.jdbc.Driver
# 配置插件信息
mybatis-plus:
mapper-locations: classpath:/mapper/**/*.xml
global-config:
db-config:
id-type: auto
logic-delete-value: 1
logic-not-delete-value: 0
# 配置日志级别
logging:
level:
com.atguigu.gulimall: debug
mybatis的分页插件信息(根据官方的版本引入存在差异https://mp.baomidou.com/guide/page.html ) 目前使用旧版本
# MyBatisConfig
@Configuration//标注配置
@EnableTransactionManagement//开启事务
@MapperScan("com.walid.youkemall.product.dao") //扫描数据接口
public class MyBatisConfig {
// 引入分页插件
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
paginationInterceptor.setLimit(500);
// 开启 count 的 join 优化,只针对部分 left join
paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
return paginationInterceptor;
}
}
一个实体类展示 配合lombok
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 品牌id
*/
@NotNull(message = "修改必须指定品牌id",groups = {UpdateGroup.class})
@Null(message = "新增不能指定id",groups = {AddGroup.class})
@TableId
private Long brandId;
/**
* 品牌名
*/
@NotBlank(message = "品牌名必须提交",groups = {AddGroup.class,UpdateGroup.class})
private String name;
/**
* 品牌logo地址
*/
@NotBlank(groups = {AddGroup.class})
@URL(message = "logo必须是一个合法的url地址",groups={AddGroup.class,UpdateGroup.class})
private String logo;
/**
* 介绍
*/
private String descript;
/**
* 显示状态[0-不显示;1-显示]
*/
// @Pattern()
@NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
@ListValue(vals={0,1},groups = {AddGroup.class, UpdateStatusGroup.class})
private Integer showStatus;
/**
* 检索首字母
*/
@NotEmpty(groups={AddGroup.class})
@Pattern(regexp="^[a-zA-Z]$",message = "检索首字母必须是一个字母",groups={AddGroup.class,UpdateGroup.class})
private String firstLetter;
/**
* 排序
*/
@NotNull(groups={AddGroup.class})
@Min(value = 0,message = "排序必须大于等于0",groups={AddGroup.class,UpdateGroup.class})
private Integer sort;
}
6. 阿里云存储(oss)
展示一个云存储的示例 (此处分为版本一和版本二, 其中版本一引入有问题 可以用版本二解决)
版本一
pom.xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alicloud-oss</artifactId>
</dependency>
application.yml
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
alicloud:
access-key: 【填写自己的】
secret-key: 【填写自己的】
oss:
endpoint: 【填写自己的】
bucket: 【填写自己的】
具体的展示方式
@RestController
public class OssController {
@Autowired
OSS ossClient;
@Value("${spring.cloud.alicloud.oss.endpoint}")
private String endpoint;
@Value("${spring.cloud.alicloud.oss.bucket}")
private String bucket;
@Value("${spring.cloud.alicloud.access-key}")
private String accessId;
@RequestMapping("/oss/policy")
public R policy() {
String host = "https://" + bucket + "." + endpoint;
// host的格式为 bucketname.endpoint
// callbackUrl为 上传回调服务器的URL,请将下面的IP和Port配置为您自己的真实信息。
// String callbackUrl = "http://88.88.88.88:8888";
String format = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
String dir = format + "/"; // 用户上传文件时指定的前缀。
Map<String, String> respMap = null;
try {
long expireTime = 30;
long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
Date expiration = new Date(expireEndTime);
PolicyConditions policyConds = new PolicyConditions();
policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
byte[] binaryData = postPolicy.getBytes("utf-8");
String encodedPolicy = BinaryUtil.toBase64String(binaryData);
String postSignature = ossClient.calculatePostSignature(postPolicy);
respMap = new LinkedHashMap<String, String>();
respMap.put("accessid", accessId);
respMap.put("policy", encodedPolicy);
respMap.put("signature", postSignature);
respMap.put("dir", dir);
respMap.put("host", host);
respMap.put("expire", String.valueOf(expireEndTime / 1000));
// respMap.put("expire", formatISO8601Date(expiration));
} catch (Exception e) {
// Assert.fail(e.getMessage());
System.out.println(e.getMessage());
}
return R.ok().put("data",respMap);
}
}
版本二
pom.xml
<!-- 引入oss的相关依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>aliyun-oss-spring-boot-starter</artifactId>
<version>1.0.0</version>
<exclusions>
<!-- 排除aliyun-java-sdk-oss 版本过低 -->
<exclusion>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-oss</artifactId>
</exclusion>
<!-- 排除aliyun-sdk-oss,版本太低,是3.1版本的-->
<exclusion>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 导入新的依赖-->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.5.7</version>
</dependency>
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.10.2</version>
</dependency>
<!-- 引入Oss依赖结束-->
<dependencyManagement>
<dependencies>
<!-- 解决导入aliyun-oss-spring-boot-starter导入依赖报错unknown -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>aliyun-spring-boot-dependencies</artifactId>
<version>1.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
application.properties
management.endpoints.jmx.exposure.include=*
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
# spring cloud access&secret config
# 可以访问如下地址查看: https://usercenter.console.aliyun.com/#/manage/ak
# 阿里巴巴oss对象存储
alibaba.cloud.access-key=【填写自己的】
alibaba.cloud.secret-key=【填写自己的】
alibaba.cloud.oss.endpoint=【填写自己的】
alibaba.cloud.oss.bucket=【填写自己的】
具体调用方式一致 也可以参照官方技术文档
7. 数据校验(JSR303和自定义)
校验分为采用JSR303进行校验 同时也可以采用自定义校验
pom.xml(父摸快)
<!-- java 注解校验 start-->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.17.Final</version>
<scope>compile</scope>
</dependency>
<!-- java 注解校验 end-->
具体的使用参照第一小点和第五小点
8. 采用枚举的方式进行控制魔法值
简单举例(父模块)
public class WareConstant {
public enum PurchaseStatusEnum{
CREATED(0,"新建"),ASSIGNED(1,"已分配"),
RECEIVE(2,"已领取"),FINISH(3,"已完成"),
HASERROR(4,"有异常");
private int code;
private String msg;
PurchaseStatusEnum(int code,String msg){
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
public enum PurchaseDetailStatusEnum{
CREATED(0,"新建"),ASSIGNED(1,"已分配"),
BUYING(2,"正在采购"),FINISH(3,"已完成"),
HASERROR(4,"采购失败");
private int code;
private String msg;
PurchaseDetailStatusEnum(int code,String msg){
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
}
9. 全局异常集中处理及slf4j进行日志管理
/**
* 集中处理所有异常
*/
@Slf4j
@RestControllerAdvice(basePackages = "com.walid.product.controller")
public class WalidExceptionControllerAdvice {
@ExceptionHandler(value= MethodArgumentNotValidException.class)
public R handleVaildException(MethodArgumentNotValidException e){
log.error("数据校验出现问题{},异常类型:{}",e.getMessage(),e.getClass());
BindingResult bindingResult = e.getBindingResult();
Map<String,String> errorMap = new HashMap<>();
bindingResult.getFieldErrors().forEach((fieldError)->{
errorMap.put(fieldError.getField(),fieldError.getDefaultMessage());
});
return R.error(BizCodeEnume.VAILD_EXCEPTION.getCode(),BizCodeEnume.VAILD_EXCEPTION.getMsg()).put("data",errorMap);
}
@ExceptionHandler(value = Throwable.class)
public R handleException(Throwable throwable){
log.error("错误:{}",throwable);
return R.error(BizCodeEnume.UNKNOW_EXCEPTION.getCode(),BizCodeEnume.UNKNOW_EXCEPTION.getMsg());
}
}