MCN是一个基于SpringBoot和SpringCloud的一个快速构建JavaWeb系统的工具。MCN在SpringBoot的基础上提供更多的默认配置以及更多的自动配置,如多数据源配置、跨域、编码、数据完整性校验等等,同时支持Servlet和Reactive以及非Web开发模式。

示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。
<parent>
<artifactId>mcn-boot-starter-parentartifactId>
<groupId>cn.hiboot.mcngroupId>
<version>${最新稳定版}version>
parent>
<dependencies>
<dependency>
<groupId>cn.hiboot.mcngroupId>
<artifactId>mcn-spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>cn.hiboot.mcngroupId>
<artifactId>mvc-swagger2artifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<scope>providedscope>
dependency>
dependencies>
<build>
<finalName>${project.artifactId}finalName>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-surefire-pluginartifactId>
<configuration>
<skipTests>trueskipTests>
configuration>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-jar-pluginartifactId>
<configuration>
<archive>
<manifestEntries>
<Build-Timestamp>${timestamp}Build-Timestamp>
<Implementation-Version>${project.version}Implementation-Version>
manifestEntries>
archive>
configuration>
plugin>
<plugin>
<groupId>org.codehaus.mojogroupId>
<artifactId>buildnumber-maven-pluginartifactId>
<configuration>
<timestampFormat>yyyy-MM-dd HH:mm:sstimestampFormat>
configuration>
<executions>
<execution>
<goals>
<goal>create-timestampgoal>
goals>
execution>
executions>
plugin>
plugins>
build>
package cn.hiboot.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 应用启动类
*
* @author DingHao
* @since 2020/5/27 10:19
*/
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
package cn.hiboot.demo.rest;
import cn.hiboot.demo.bean.DemoBean;
import cn.hiboot.mcn.core.model.result.RestResp;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/**
* rest接口
*
* @author DingHao
* @since 2020/5/27 10:20
*/
@RequestMapping("demo")
@RestController
@Validated
@Api(tags = "demo接口")
public class DemoRestApi {
@GetMapping("list")
@ApiOperation("列表")
public RestResp<String> list(String query) {
return new RestResp(query);
}
@PostMapping("json")
@ApiOperation("post json")
public RestResp<DemoBean> postJson(@Validated @RequestBody DemoBean userBean) {
return new RestResp(userBean);
}
}
#一般与项目模块对应
spring.application.name=demo
#开启swagger
swagger.enable=true
下表提供了可用于覆盖 MCN 管理的版本的所有属性。应用可自定义这些版本。
| 库 | 版本属性 | 当前版本 |
|---|---|---|
| bcprov-jdk15on | bcprov-jdk15on.version | 1.70 |
| bcpkix-jdk18on | bcprov-jdk18on.version | 1.71 |
| spring-boot-admin-starter-client | boot-admin.version | 2.7.7 |
| spring-boot-admin-starter-server | boot-admin.version | 2.7.7 |
| commons-io | commons-io.version | 2.11.0 |
| fastjson | fastjson.version | 1.2.83 |
| fastjson2 | fastjson2.version | 2.0.14 |
| guava | guava.version | 31.1-jre |
| hutool-core | hutool.version | 5.8.9 |
| jsoup | jsoup.version | 1.15.3 |
| knife4j-spring-ui | knife4j.version | 3.0.3 |
| mapstruct | mapstruct.version | 1.5.2.Final |
| mybatis-spring-boot-starter | mybatis-spring-boot.version | 2.2.2 |
| minio | minio.version | 8.4.3 |
| snakeyaml | snakeyaml.version | 1.32 |
| spring-boot-dependencies | spring-boot.version | 2.7.4 |
| spring-cloud-dependencies | spring-cloud.version | 2021.0.4 |
| spring-cloud-alibaba-dependencies | spring-cloud-alibaba.version | 2021.1 |
| springfox-swagger2 | springfox-swagger.version | 3.0.0 |
| springfox-swagger-ui | springfox-swagger.version | 3.0.0 |
| swagger-models | swagger.version | 1.6.4 |
| swagger-bootstrap-ui | swagger-bootstrap-ui.version | 1.9.6 |
| xxl-job-core | xxl-job.version | 2.3.1 |
package cn.hiboot.mcn.core.model.result;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonInclude(JsonInclude.Include.NON_NULL)//Null字段不返回
public class RestResp<T> {
public enum ActionStatusMethod {
OK,
FAIL
}
@JsonProperty("ActionStatus")//返回字段名大写,默认OK表示正常结果返回
private ActionStatusMethod ActionStatus = ActionStatusMethod.OK;
@JsonProperty("ErrorCode")//返回字段名大写,默认0表示无错误
private Integer ErrorCode = 0;
@JsonProperty("ErrorInfo")//返回字段名大写,错误具体信息(当异常返回时)
private String ErrorInfo = "";
@JsonProperty("Duration")//接口执行时间需结合注解@Timing使用
private Long duration;
private T data;//接口返回的数据
private Long count;//数据返回的count数,分页时使用
//省略set/get
}
::: warning 注意
当在SpringBoot项目里使用时,IOC容器中存在ObjectMapper则优先使用外部的
:::
::: warning 注意
当存在多个上下文时且当前上下文是子上下文时会更新applicationContext
:::
系统内置三种属性源名称分别是:mcn-global-unique、mcn-map、mcn-default
::: tip 提示
当启用了spring.profiles.active配置同时会加载文件mcn-{profile}.properties,当激活多个profile时且存在相同属性配置时后面的优先级比前面的高
:::
该属性源里只包含五个配置(内部使用):
该属性源里包含很多配置如:
#内嵌tomcat根路径
server.tomcat.basedir=/tmp/tomcat/
#启用tomcat数据压缩
server.compression.enabled=true
::: warning 注意
除了mcn-global-unique之外其它属性源默认不会加引导上下文中加载
当配置mcn.bootstrap.eagerLoad.enable=true可启用其它三个属性源也在引导上下文中加载
当环境中存在mcn-default属性源,则mcn-default、mcn-map属性源都不会加载
日志文件按天压缩,自动删除30天前日志
:::
调试时使用,生产应关闭!!!
当环境中配置mcn.print-env.enable=true时,项目在启动时会自动打印所有可枚举的属性源。
public class UserService{
private UserMapper userMapper;
public UserService(UserMapper userMapper){
this.userMapper = userMapper;
}
public void login(String username,String password){
if(userMapper.find(username) == null){
throw ServiceException.newInstance("用户不存在");
}
}
}
public class CustomException extends BaseException{
public CustomException(Throwable cause) {
super(cause);
}
}
public class CustomException extends RuntimeException{
}
::: warning 注意
自定义一个处理HttpMessageNotReadableException的异常处理器。
@Configuration
public class CustomExceptionResolver {
@Bean
public ExceptionResolver specialSymbolExceptionResolver() {
return new ExceptionResolver() {
@Override
public boolean support(HttpServletRequest request, Throwable t) {
return t instanceof HttpMessageNotReadableException;
}
@Override
public RestResp<Object> resolveException(HttpServletRequest request, Throwable t) {
ServiceException serviceException = ServiceException.find(t);
if (serviceException == null || serviceException.getCode() != ExceptionKeys.SPECIAL_SYMBOL_ERROR) {
return null;
}
return RestResp.error(serviceException.getCode(), serviceException.getMessage());
}
};
}
}
#配置允许的请求方式
mcn.cors.allowed-method=GET,POST
#配置允许的请求来源
mcn.cors.allowed-origin=https://www.xxx.com/
#配置允许的请求头
mcn.cors.allowed-header=TSM
#配置拦截的路径模式,默认/**即拦截所有
mcn.cors.pattern=/**
::: tip 提示
生产环境一定要关闭跨域设置
:::
#是否启用参数处理
mcn.xss.enable=true
#指定哪些接口需要处理(ant匹配模式)
mcn.xss.exclude-urls=
#指定哪些字段不做处理(全局生效),如mcn.xss.exclude-fields=name,则所有接口中name字段不处理
mcn.xss.exclude-fields=
#指定哪些接口需要处理(ant匹配模式),默认/**
mcn.xss.include-urls=/**
::: tip 提示
生产环境建议开启XSS设置
:::
web.security.default-exclude-urls=/v2/api-docs,/swagger-resources/**,/doc.html,/webjars/**,/error,/favicon.ico,/_imagePreview,/*.png,/_groovyDebug_
#配置不拦截的路径
web.security.exclude-urls==/user/**,/news/add
::: tip 提示
可通过web.security.enable-default-ignore=false关闭默认忽略的路径
:::
::: tip 提示
@Phone 仅简单校验了11位数字手机号
:::
通过前缀multiple.datasource配置数据源即代表使用的是多数据源配置。
multiple.datasource.hello.url=jdbc:mysql://127.0.0.1:3306/test?createDatabaseIfNotExist=true&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false&useSSL=false&serverTimezone=Asia/Shanghai
multiple.datasource.hello.username=root
multiple.datasource.hello.password=123456
multiple.datasource.world.url=jdbc:mysql://127.0.0.1:3306/web_template?createDatabaseIfNotExist=true&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false&useSSL=false&serverTimezone=Asia/Shanghai
multiple.datasource.world.username=root
multiple.datasource.world.password=123456
dynamic.datasource.enable=true
在MyBatis和Jpa使用动态数据源
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
@RequestMapping("test")
@RestController
@SwitchSource("hello")
public class TestRestApi {
private UserDao userDao;
public TestRestApi(UserDao userDao) {
this.userDao = userDao;
}
@GetMapping("list")
public RestResp<List<User>> list() {
return new RestResp<>(userDao.findAll());
}
@GetMapping("list2")
@SwitchSource("world")
public RestResp<List<User>> list2() {
return new RestResp<>(userDao.findAll());
}
}
::: tip 提示
SwitchSource注解既可以用在类上也可以用在方法上,方法上的优先级高
:::
MyBatis和Jpa使用多数据源
#同时只能启动一个
#mybatis.multiple.datasource.enable=true
jpa.multiple.datasource.enable=true
dao层必须在启动类所在包的子包dao下且用数据源的名称当子包名称,如下图所示
@RequestMapping("test")
@RestController
public class TestRestApi {
private UserDao userDao;
private UserDao2 userDao2;
public TestRestApi(UserDao userDao, UserDao2 userDao2) {
this.userDao = userDao;
this.userDao2 = userDao2;
}
@GetMapping("list3")
public RestResp<List<User>> list3() {
List<User> all = userDao2.findAll();
all.addAll(userDao.findAll());
return new RestResp<>(all);
}
}
::: warning 注意
使用SM2加密配置
使用方式
<dependencies>
<dependency>
<groupId>org.bouncycastlegroupId>
<artifactId>bcprov-jdk18onartifactId>
dependency>
<dependency>
<groupId>cn.hutoolgroupId>
<artifactId>hutool-cryptoartifactId>
dependency>
dependencies>
encryptor.sm2.private-key=308193020100301306072a8648ce3d020106082a811ccf5501822d04793077020101042044880abf2572c0946e4d7a18a812fe554f20db40bc852a3b78b7e057af72344ca00a06082a811ccf5501822da14403420004258785f13a181c19fe366f13e1e4d93834944fb6b2d05b0ef58963e3cbdc3d680b228bdeab895a1f113dd6690279cd932ab85ecf694ebf34d6201b9d76055094
encryptor.sm2.public-key=3059301306072a8648ce3d020106082a811ccf5501822d03420004258785f13a181c19fe366f13e1e4d93834944fb6b2d05b0ef58963e3cbdc3d680b228bdeab895a1f113dd6690279cd932ab85ecf694ebf34d6201b9d76055094
@RequestMapping("test")
@RestController
public class TestRestApi {
private final TextEncryptor textEncryptor;
public TestRestApi(TextEncryptor textEncryptor) {
this.textEncryptor = textEncryptor;
}
@GetMapping("encrypt")
public RestResp<String> encrypt(String text) {
return new RestResp<>(textEncryptor.encrypt(text));
}
@GetMapping("decrypt")
public RestResp<String> decrypt(String text) {
return new RestResp<>(textEncryptor.decrypt(text));
}
}
只需要在需要解密的参数上添加注解@Decrypt即可
@RequestMapping("test")
@RestController
public class TestRestApi {
@GetMapping("list")
public RestResp<String> list(@Decrypt String query) {
return new RestResp<>(query);
}
}
只需要在需要解密的字段上添加注解@Decrypt即可
@RequestMapping("test")
@RestController
public class TestRestApi {
@PostMapping("add")
public RestResp<User> add( @RequestBody User userBean) {
return new RestResp<>(userBean);
}
}
@Setter
@Getter
public class User{
private Integer id;
@Decrypt
private String name;
private Integer age;
}
使用SM3计算摘要
使用方式
<dependencies>
<dependency>
<groupId>org.bouncycastlegroupId>
<artifactId>bcprov-jdk18onartifactId>
dependency>
<dependency>
<groupId>cn.hutoolgroupId>
<artifactId>hutool-cryptoartifactId>
dependency>
dependencies>
#开启完整性校验
data.integrity.enable=true
#指定哪些接口不需要校验(ant匹配模式)
data.integrity.exclude-patterns=
#指定哪些接口需要校验(ant匹配模式),默认/**
data.integrity.include-patterns=/**
#指定是否在feign调用中也校验,默认false不校验
#data.integrity.interceptor.enable=false
在header里出入以下三个参数timestamp(时间戳)、nonceStr(随机字符串)、signature(签名)
@RequestMapping("test")
@RestController
public class TestRestApi {
@GetMapping("list")
public RestResp<String> list(String query) {
return new RestResp<>(query);
}
}
::: tip 提示
如果不在header里传入相关参数或者校验失败会提示
{
"ActionStatus": "FAIL",
"ErrorCode": 999999,
"ErrorInfo": "验证失败,数据被篡改"
}
:::
使用SM4加密配置
使用方式
<dependencies>
<dependency>
<groupId>org.bouncycastlegroupId>
<artifactId>bcprov-jdk18onartifactId>
dependency>
<dependency>
<groupId>cn.hutoolgroupId>
<artifactId>hutool-cryptoartifactId>
dependency>
dependencies>
以下配置必须放在classpath:config/mcn.properties文件中
#加密key,必须128位即16字节
encryptor.sm4.key=abcdefghijklmnop
如在application.properties中存在加密项spring.datasource.username,而真正使用的是解密后的值root
spring.datasource.username={cipher}7eda434344898ba11617084e0f117103
@RequestMapping("test")
@RestController
public class TestRestApi {
private UserDao userDao;
private TextEncryptor textEncryptor;
public TestRestApi(UserDao userDao, TextEncryptor textEncryptor) {
this.userDao = userDao;
this.textEncryptor = textEncryptor;
}
@PostMapping("add")
public RestResp<User> add( @RequestBody User userBean) {
//入库手机号加密
userBean.setMobile(textEncryptor.encrypt(userBean.getMobile()));
userDao.save(userBean);
return new RestResp<>(userBean);
}
}
::: warning 注意
对输入的参数的统一处理,支持kv编码和json编码参数
#是否启用参数处理
param.processor.enable=true
#指定哪些接口需要校验(ant匹配模式)
param.processor.exclude-urls=
#指定哪些字段不做处理(全局生效),如param.processor.exclude-fields=name,则所有接口中name字段不处理
param.processor.exclude-fields=
#指定哪些接口需要校验(ant匹配模式),默认/**
param.processor.include-urls=/**
#全局校验规则(正则表达式),默认的参数处理器ParamProcessor是基于正则的
global.rule.pattern=.*
@Component
public class CustomRule{
@Bean
public ParamProcessor myParamProcessor() {
return (rule, name, value) -> {
//这里可以编写校验逻辑
//rule:当前规则
//name:当前字段名
//value:当前字段值
Pattern pattern = Pattern.compile(rule);
if(pattern.matcher(value).matches()){
throw ServiceException.newInstance(ExceptionKeys.SPECIAL_SYMBOL_ERROR);
}
return value;
};
}
}
通过在参数上指定注解@CheckParam来定义各字段不同的校验规则
@Setter
@Getter
@CheckParam("[a-z0-9]+")
public class User{
private Integer id;
@CheckParam("[a-z]+")
private String name;
private Integer age;
}
默认获取锁及锁持有的时间都是5秒。
@RequestMapping("test")
@RestController
public class TestRestApi {
@GetMapping("list")
@DistributedLock("list_lock")
public RestResp<String> list(String query) {
return new RestResp<>(query);
}
}
@RequestMapping("test")
@RestController
public class TestRestApi {
private static final String LIST_LOCK_NAME = "list_lock";
private final DistributedLocker distributedLocker;
public TestRestApi(DistributedLocker distributedLocker) {
this.distributedLocker = distributedLocker;
}
@GetMapping("list")
@DistributedLock("list_lock")
public RestResp<String> list(String query) {
if(distributedLocker.tryLock(LIST_LOCK_NAME,10,10)){//获取锁等待超时10秒持有超时时间10秒
//do something
distributedLocker.unlock(LIST_LOCK_NAME);
}
return new RestResp<>(query);
}
}
扩展原有的数据库初始化即默认情况处理classpath*下的db文件夹下的脚本,另额外添加文件名为other的sql文件解析并可指定一个分隔符。
示例:自动初始化一张表并须支持不同环境,如MySQL和kingbase的初始化脚本。
<dependencies>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>com.kingbase8groupId>
<artifactId>kingbase8artifactId>
<version>8.6.0version>
dependency>
dependencies>
spring.datasource.url=jdbc:${spring.sql.init.platform}://127.0.0.1:3306/test?createDatabaseIfNotExist=true&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=111111
spring.sql.init.mode=always
#指定当前应用运行环境使用的是mysql
spring.sql.init.platform=mysql
spring.sql.init.continue-on-error=true
schema-mysql.sql内容如下:
CREATE TABLE t_user (
id int(11) NOT NULL AUTO_INCREMENT,
name varchar(64) NOT NULL,
age int(11) DEFAULT NULL,
create_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
schema-kingbase8.sql内容如下:
CREATE TABLE t_user (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(64) NOT NULL,
age INT DEFAULT NULL,
create_at timestamp DEFAULT sysdate NOT NULL,
update_at timestamp DEFAULT sysdate NOT NULL,
PRIMARY KEY ("id")
);
other-kingbase8.sql内容如下:
create or replace trigger t_user_trigger
before
update on t_user
for each row
begin
select sysdate into :NEW.update_at from dual;
end;
//
::: tip 提示
#系统会与该init_db建立连接再创建test库
spring.sql.init.additional.init-db-name=init_db
:::