欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

java開源項(xiàng)目jeecgboot的超詳細(xì)解析

 更新時(shí)間:2022年10月21日 11:27:59   作者:小鮑侃java  
JeecgBoot是一款基于BPM的低代碼平臺(tái),下面這篇文章主要給大家介紹了關(guān)于java開源項(xiàng)目jeecgboot的相關(guān)資料,文中通過(guò)圖文以及實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下

一.搭建

1.前端

npm install
npm run serve

2.后端

老生常談的配置,修改mysql與redis即可。

二.業(yè)務(wù)功能介紹

功能上jeecgboot主要提供了系列的代碼生成器、模板頁(yè)面、報(bào)表頁(yè)面。

1.報(bào)表功能

主要提供報(bào)表的相關(guān)操作。提供了積木報(bào)表插件,可以自定義數(shù)據(jù)報(bào)表、圖形報(bào)表。并將報(bào)表掛載到菜單上。

2.在線開發(fā)

也就是代碼生成器,可以可視化的在頁(yè)面上新建數(shù)據(jù)庫(kù)表,并通過(guò)數(shù)據(jù)庫(kù)表生成前后臺(tái)代碼。減少業(yè)務(wù)代碼開發(fā)的時(shí)間。

3.系統(tǒng)管理

用戶管理、角色管理、機(jī)構(gòu)管理、消息管理等基礎(chǔ)模塊。

4.系統(tǒng)監(jiān)控

主要負(fù)責(zé)各種日志、監(jiān)控的統(tǒng)一處理。

5.頁(yè)面組件樣式

常見案例、詳情頁(yè)、結(jié)果頁(yè)、異常頁(yè)、列表頁(yè)、表單頁(yè)主要提供了樣式頁(yè)面與控件頁(yè)面示例。在開發(fā)過(guò)程中如果需要模板直接復(fù)制代碼即可。詳情請(qǐng)

三.后臺(tái)架構(gòu)介紹

1.概括

其中報(bào)表和代碼生成器沒(méi)有提供源碼,如果有興趣可以自行查看jar包源碼。

2.架構(gòu)核心包jeecg-boot-base

jeecg-boot-base包括了下文的幾個(gè)部分。

1.接口包jeecg-boot-base-api

1.對(duì)外接口jeecg-system-cloud-api

使用feign+hystrix實(shí)現(xiàn)了服務(wù)間調(diào)用加熔斷,單機(jī)環(huán)境并沒(méi)有使用。

2.服務(wù)內(nèi)接口jeecg-system-local-api

該包提供了下文使用的常用方法接口。僅提供了接口并無(wú)其他配置。

2.核心配置包jeecg-boot-base-core

1.通用類common 1.api

其中為通用接口與通用返回對(duì)象。

1.Result

其中Result為所有類的返回實(shí)體,這樣能夠通過(guò)code編碼和message獲取是否成功和成功/失敗的信息。此類是常用的架構(gòu)設(shè)計(jì)

2.aspect

為項(xiàng)目的自定義注解,使用了AOP的切面方式實(shí)現(xiàn),這里就不詳細(xì)說(shuō)了,比較簡(jiǎn)單都可以看懂。

3.constant

存放著枚舉類與常量池,這里不多說(shuō)了。

4.es

為操作es的通用類,主要是配置es連接和查詢時(shí)動(dòng)態(tài)拼接and/or的方法。

5.exception

exception為自定義的異常類。

1.JeecgBootExceptionHandler

這里詳細(xì)說(shuō)一下JeecgBootExceptionHandler,該類也是常見的架構(gòu)設(shè)計(jì)之一,核心為@RestControllerAdvice、@ExceptionHandler。當(dāng)業(yè)務(wù)代碼中沒(méi)有對(duì)異常攔截時(shí),該類會(huì)自動(dòng)攔截異常,并數(shù)據(jù)log日志。所以某些日志在該類配置后,就不需要在每個(gè)接口中都捕獲這個(gè)異常了。

6.handler

為下文規(guī)范提供了接口類。沒(méi)有其他特別說(shuō)明。

7.system類

這里主要說(shuō)controller、entity、service等業(yè)務(wù)代碼的父類

1.JeecgController<T, S extends IService>

所以controller的父類,提供了導(dǎo)入導(dǎo)出的功能。還可以在里面擴(kuò)展分頁(yè)、排序、常用調(diào)用方法等,這樣就可以避免相同的代碼多次添加。這也是架構(gòu)設(shè)計(jì)中常用的技巧。

2.JeecgEntity

將通用字段如id、創(chuàng)建人、修改人、創(chuàng)建時(shí)間、修改時(shí)間等字段統(tǒng)一封裝在一個(gè)實(shí)體中,使用其他實(shí)體繼承。這也是架構(gòu)設(shè)計(jì)中常用的技巧。

3.service

主要提供Mybatis-plus提供的curd方法。

8.utli

提供了一大波的工具類,如果在工作中需要,直接復(fù)制使用。

2.通用配置類config

1.mybatis

1.MybatisInterceptor

MybatisInterceptor這里主要說(shuō)MybatisInterceptor,該類負(fù)責(zé)在mybatis執(zhí)行語(yǔ)句前,攔截并獲取參數(shù),將創(chuàng)建人、創(chuàng)建時(shí)間等字符動(dòng)態(tài)插入。這里上部分核心代碼。

MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
		String sqlId = mappedStatement.getId();
		log.debug("------sqlId------" + sqlId);
		//獲取sql類型是插入還是修改
		SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
		//獲取插入?yún)?shù)
		Object parameter = invocation.getArgs()[1];
		if (parameter == null) {
			return invocation.proceed();
		}
		if (SqlCommandType.INSERT == sqlCommandType) {
			LoginUser sysUser = this.getLoginUser();
			//通過(guò)反射獲取入?yún)⒌念?
			Field[] fields = oConvertUtils.getAllFields(parameter);
			for (Field field : fields) {
				log.debug("------field.name------" + field.getName());
				try {
				    //將創(chuàng)建人信息動(dòng)態(tài)加入
					if ("createBy".equals(field.getName())) {
						field.setAccessible(true);
						Object local_createBy = field.get(parameter);
						field.setAccessible(false);
						if (local_createBy == null || local_createBy.equals("")) {
							if (sysUser != null) {
								// 登錄人賬號(hào)
								field.setAccessible(true);
								field.set(parameter, sysUser.getUsername());
								field.setAccessible(false);
							}
						}
					}
			}		

2.MybatisPlusSaasConfig

該類主要負(fù)責(zé)多租戶,什么是多租戶呢?

多租戶:就是多個(gè)公司/客戶公用一套系統(tǒng)/數(shù)據(jù)庫(kù),這就需要保證數(shù)據(jù)的權(quán)限。

該場(chǎng)景比較少不詳細(xì)說(shuō)明。

2.oss

主要從application-dev.yml獲取到上傳的路徑與配置。

3.shiro

安全框架主要有兩個(gè)目標(biāo):認(rèn)證與鑒權(quán)。

認(rèn)證:判斷用戶名密碼是否正確。

鑒權(quán):判斷用戶是否有權(quán)限訪問(wèn)該接口。

這里本文著重講解,如果遇到shiro相關(guān)應(yīng)用,可以項(xiàng)目直接移植使用。

1.CustomShiroFilterFactoryBean

該類主要負(fù)責(zé)解決資源中文路徑問(wèn)題。這里有個(gè)通用的解決方式。

新建類集成ShiroFilterFactoryBean方法,并重寫核心方法createInstance(),并在注入時(shí),注入新建的類CustomShiroFilterFactoryBean,這樣就達(dá)到的以往重新源碼的功能。因?yàn)閟pring提供的功能都是用該思想,所以修改源碼的地方就原來(lái)越少了,都可以使用該方式實(shí)現(xiàn)。

2.JwtFilter

同上文,復(fù)寫BasicHttpAuthenticationFilter的驗(yàn)證登錄用戶的方法,在執(zhí)行登錄接口后判斷用戶是否正確。

3.ResourceCheckFilter

負(fù)責(zé)鑒權(quán)使用,判斷當(dāng)前用戶是否有權(quán)限訪問(wèn)。

    //表示是否允許訪問(wèn) ,如果允許訪問(wèn)返回true,否則false;
    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
        Subject subject = getSubject(servletRequest, servletResponse);
        //獲取當(dāng)前url
        String url = getPathWithinApplication(servletRequest);
        log.info("當(dāng)前用戶正在訪問(wèn)的 url => " + url);
        return subject.isPermitted(url);
    }

    //onAccessDenied:表示當(dāng)訪問(wèn)拒絕時(shí)是否已經(jīng)處理了; 如果返回 true 表示需要繼續(xù)處理; 如果返回 false
    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        response.sendRedirect(request.getContextPath() + this.errorUrl);
        // 返回 false 表示已經(jīng)處理,例如頁(yè)面跳轉(zhuǎn)啥的,表示不在走以下的攔截器了(如果還有配置的話)
        return false;
    }

4.ShiroRealm

主要負(fù)責(zé)獲取用戶所有的菜單權(quán)限,并提供token的一系列方法。

    //獲取所有菜單權(quán)限集合
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {  
    }

    //驗(yàn)證用戶輸入的賬號(hào)和密碼是否正確
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
    }

    //校驗(yàn)token的有效性
    public LoginUser checkUserTokenIsEffect(String token) throws AuthenticationException {
    }

    //刷新token有效時(shí)間
    public boolean jwtTokenRefresh(String token, String userName, String passWord) {
    }

    //清除當(dāng)前用戶的權(quán)限認(rèn)證緩存
    @Override
    public void clearCache(PrincipalCollection principals) {
        super.clearCache(principals);
    }

5.ShiroConfig

此為shiro的核心配置類,大多數(shù)寫法都是固定寫法。

public class ShiroConfig {

    @Value("${jeecg.shiro.excludeUrls}")
    private String excludeUrls;
    @Resource
    LettuceConnectionFactory lettuceConnectionFactory;
    @Autowired
    private Environment env;


    /**
     * Filter Chain定義說(shuō)明
     *
     * 1、一個(gè)URL可以配置多個(gè)Filter,使用逗號(hào)分隔
     * 2、當(dāng)設(shè)置多個(gè)過(guò)濾器時(shí),全部驗(yàn)證通過(guò),才視為通過(guò)
     * 3、部分過(guò)濾器可指定參數(shù),如perms,roles
     */
    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        CustomShiroFilterFactoryBean shiroFilterFactoryBean = new CustomShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 攔截器
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        if(oConvertUtils.isNotEmpty(excludeUrls)){
            String[] permissionUrl = excludeUrls.split(",");
            for(String url : permissionUrl){
                filterChainDefinitionMap.put(url,"anon");
            }
        }
        // 配置不會(huì)被攔截的鏈接 順序判斷 也就是不同通過(guò)token訪問(wèn)的地址
        filterChainDefinitionMap.put("/sys/cas/client/validateLogin", "anon"); /
  
        // 添加自己的過(guò)濾器并且取名為jwt
        Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
        //如果cloudServer為空 則說(shuō)明是單體 需要加載跨域配置【微服務(wù)跨域切換】
        Object cloudServer = env.getProperty(CommonConstant.CLOUD_SERVER_KEY);
        //前文定義的過(guò)濾器
        filterMap.put("jwt", new JwtFilter(cloudServer==null));
        shiroFilterFactoryBean.setFilters(filterMap);
        // <!-- 過(guò)濾鏈定義,從上向下順序執(zhí)行,一般將/**放在最為下邊
        filterChainDefinitionMap.put("/**", "jwt");

        // 未授權(quán)界面返回JSON
        shiroFilterFactoryBean.setUnauthorizedUrl("/sys/common/403");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    //加入security自定義配置類
    @Bean("securityManager")
    public DefaultWebSecurityManager securityManager(ShiroRealm myRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //加入上文提供的授權(quán)配置
        securityManager.setRealm(myRealm);
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);
        //使用redis緩存有關(guān)信息(實(shí)現(xiàn)在下文)
        securityManager.setCacheManager(redisCacheManager());
        return securityManager;
    }

    //下面的代碼是添加注解支持
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        defaultAdvisorAutoProxyCreator.setUsePrefix(true);
        defaultAdvisorAutoProxyCreator.setAdvisorBeanNamePrefix("_no_advisor");
        return defaultAdvisorAutoProxyCreator;
    }

    //管理shiro生命周期
    @Bean
    public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    //開啟shiro注解
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

    // 使用redis緩存用戶信息 ,并設(shè)置redis
    public RedisCacheManager redisCacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        //配置redis實(shí)例
        redisCacheManager.setRedisManager(redisManager());
        //redis中針對(duì)不同用戶緩存(此處的id需要對(duì)應(yīng)user實(shí)體中的id字段,用于唯一標(biāo)識(shí))
        redisCacheManager.setPrincipalIdFieldName("id");
        //用戶權(quán)限信息緩存時(shí)間
        redisCacheManager.setExpire(200000);
        return redisCacheManager;
    }

     //連接redsi,分為單機(jī)與集群
    @Bean
    public IRedisManager redisManager() {
        log.info("===============(2)創(chuàng)建RedisManager,連接Redis..");
        IRedisManager manager;
        // redis 單機(jī)支持,在集群為空,或者集群無(wú)機(jī)器時(shí)候使用 add by jzyadmin@163.com
        if (lettuceConnectionFactory.getClusterConfiguration() == null || lettuceConnectionFactory.getClusterConfiguration().getClusterNodes().isEmpty()) {
            RedisManager redisManager = new RedisManager();
            redisManager.setHost(lettuceConnectionFactory.getHostName());
            redisManager.setPort(lettuceConnectionFactory.getPort());
            redisManager.setDatabase(lettuceConnectionFactory.getDatabase());
            redisManager.setTimeout(0);
            if (!StringUtils.isEmpty(lettuceConnectionFactory.getPassword())) {
                redisManager.setPassword(lettuceConnectionFactory.getPassword());
            }
            manager = redisManager;
        }else{
            // redis集群支持,優(yōu)先使用集群配置
            RedisClusterManager redisManager = new RedisClusterManager();
            Set<HostAndPort> portSet = new HashSet<>();
            lettuceConnectionFactory.getClusterConfiguration().getClusterNodes().forEach(node -> portSet.add(new HostAndPort(node.getHost() , node.getPort())));
            //update-begin--Author:scott Date:20210531 for:修改集群模式下未設(shè)置redis密碼的bug issues/I3QNIC
            if (oConvertUtils.isNotEmpty(lettuceConnectionFactory.getPassword())) {
                JedisCluster jedisCluster = new JedisCluster(portSet, 2000, 2000, 5,
                    lettuceConnectionFactory.getPassword(), new GenericObjectPoolConfig());
                redisManager.setPassword(lettuceConnectionFactory.getPassword());
                redisManager.setJedisCluster(jedisCluster);
            } else {
                JedisCluster jedisCluster = new JedisCluster(portSet);
                redisManager.setJedisCluster(jedisCluster);
            }
            manager = redisManager;
        }
        return manager;
    }

}

4.sign

這里不詳細(xì)講解,主要描述前臺(tái)傳來(lái)的簽名是否合法。

5.thirdapp

根據(jù)application-dev.yml配置獲取是否開啟第三方接入驗(yàn)證。@ConfigurationProperties(prefix = "third-app.type")獲取配置文件中的third-app.type的value值。

6.AutoPoiConfig、AutoPoiDictConfig

主要負(fù)責(zé)將excel中的數(shù)據(jù)轉(zhuǎn)換為數(shù)據(jù)字典。

7.CorsFilterCondition、JeecgCloudCondition

通過(guò)獲取application-dev.yml,主要判斷是否有CLOUD_SERVER_KEY = "spring.cloud.nacos.discovery.server-addr";的key值。

context.getEnvironment().getProperty(CommonConstant.CLOUD_SERVER_KEY);是從application-dev.yml生成的map獲取value值。

8.RestTemplateConfig

在服務(wù)間調(diào)用時(shí)設(shè)置連接時(shí)長(zhǎng),如果單體應(yīng)用,改配置沒(méi)有使用。

9.StaticConfig

從application-dev.yml獲取配置,設(shè)置靜態(tài)參數(shù)初始化。

10.Swagger2Config

Swagger2文檔配置類,如果有需要請(qǐng)執(zhí)行復(fù)制使用。

11.WebMvcConfiguration

springboot的常用配置,如跨域配置,精度丟失配置,靜態(tài)資源配置。都是固定寫法,如果需要請(qǐng)自行參考。

12.WebSocketConfig

springboot提供的websocket的start配置方式,如果有疑問(wèn)可以參考博主之前的博文-websocket的集成使用

3.業(yè)務(wù)接口modules.base

主要提供了日志相關(guān)的curd,不多做描述。

3.工具包jeecg-boot-base-tools

主要提供了一些功能的實(shí)現(xiàn)類與使用方法,不多說(shuō) ,比較簡(jiǎn)單。

1.TransmitUserTokenFilter、UserTokenContext

主要負(fù)責(zé)將token放在上下文中。

2.JeecgRedisListerer、RedisReceiver

這里是發(fā)送消息模板的封裝。核心是從上下文中的getbean方法動(dòng)態(tài)的指定想要調(diào)用的JeecgRedisListerer實(shí)現(xiàn)類。一種多態(tài)的實(shí)現(xiàn)方式。

3.JeecgRedisCacheWriter

可以看到思想還是上文所說(shuō),將RedisCacheWriter類中的方法全部復(fù)制出來(lái),并生成新類JeecgRedisCacheWriter,在新類中修改,他的目的是信息模塊在存入緩存時(shí),有統(tǒng)一的前綴。在使用時(shí),注入使用JeecgRedisCacheWriter即可,跟修改源碼有這一樣效果,但是更加優(yōu)雅??梢钥闯鰏pring的設(shè)計(jì)思想是多牛批。

3.測(cè)試包jeecg-boot-base

主要負(fù)責(zé)調(diào)用其他功能,沒(méi)啥實(shí)質(zhì)意義。

下圖類是xxljob執(zhí)行定時(shí)任務(wù)時(shí)的寫法,可以看一看。

4.業(yè)務(wù)包jeecg-boot-module-system

主要為業(yè)務(wù)代碼包,這里找?guī)讉€(gè)著重講解一下。

1.api.controller

為微服務(wù)為其他服務(wù)提供基礎(chǔ)應(yīng)用接口的包,如果沒(méi)有微服務(wù)該包不生效。

2.message

該模塊為消息模塊業(yè)務(wù),其中使用了quartz和spring提供的websocket start。如果有興趣可以參考博主給的連接。

3.monitor

提供了redis監(jiān)控的接口。如果需要可以自行查看,比較簡(jiǎn)單。

4.quartz

定時(shí)任務(wù)start,如果想具體了解可以參考博主文章:quartz集成全解析

5.jeecg-boot-starter

springboot的核心就是提供各種各樣的start,在jeecg中也提供了很多的start,分別是上述的四個(gè),其中job為xxl開源項(xiàng)目、cloud為在服務(wù)間調(diào)用時(shí),將token放再頭中,這里不詳細(xì)講解。

下文示例均在jeecg-cloud-module/jeecg-cloud-system-start中。

1.jeecg-boot-starter-rabbitmq

1.MqListener、BaseRabbiMqHandler

在監(jiān)聽消息隊(duì)列時(shí),使用以下方法即可。原因是必須加入ack與nack。防止隊(duì)列占用。

使用時(shí),該類繼承了BaseRabbiMqHandler,并使用父類的方法,并使用new MqListener<BaseMap>()函數(shù)式方法獲取消息隊(duì)列中的信息。

@Slf4j
@RabbitListener(queues = CloudConstant.MQ_JEECG_PLACE_ORDER)
@RabbitComponent(value = "helloReceiver1")
public class HelloReceiver1 extends BaseRabbiMqHandler<BaseMap> {
    @RabbitHandler
    public void onMessage(BaseMap baseMap, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) {
        //使用了父類的方法
        super.onMessage(baseMap, deliveryTag, channel, new MqListener<BaseMap>() {
            @Override
            public void handler(BaseMap map, Channel channel) {
                //業(yè)務(wù)處理
                String orderId = map.get("orderId").toString();
                log.info("MQ Receiver1,orderId : " + orderId);
            }
        });
    }
}

BaseRabbiMqHandler主要的功能是提供了ack與nack,并將token放入頭中。

@Slf4j
public class BaseRabbiMqHandler<T> {

    private String token= UserTokenContext.getToken();

    public void onMessage(T t, Long deliveryTag, Channel channel, MqListener mqListener) {
        try {
            UserTokenContext.setToken(token);
            mqListener.handler(t, channel);
            channel.basicAck(deliveryTag, false);
        } catch (Exception e) {
            log.info("接收消息失敗,重新放回隊(duì)列");
            try {
                /**
                 * deliveryTag:該消息的index
                 * multiple:是否批量.true:將一次性拒絕所有小于deliveryTag的消息。
                 * requeue:被拒絕的是否重新入隊(duì)列
                 */
                channel.basicNack(deliveryTag, false, true);
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }
}

public interface MqListener<T> {

    default void handler(T map, Channel channel) {
    }
}

2.RabbitMqClient

主要在隊(duì)列初始化時(shí)實(shí)現(xiàn)隊(duì)列的初始化,而是否初始化根據(jù)使用時(shí)的@RabbitListener、@RabbitComponent判斷。

	public interface MqListener<T> {
	    default void handler(T map, Channel channel) {
	    }
	}

    @Bean
    public void initQueue() {
        //獲取帶RabbitComponent注解的類
        Map<String, Object> beansWithRqbbitComponentMap = this.applicationContext.getBeansWithAnnotation(RabbitComponent.class);
        Class<? extends Object> clazz = null;
        //循環(huán)map
        for (Map.Entry<String, Object> entry : beansWithRqbbitComponentMap.entrySet()) {
            log.info("初始化隊(duì)列............");
            //獲取到實(shí)例對(duì)象的class信息
            clazz = entry.getValue().getClass();
            Method[] methods = clazz.getMethods();
            //判斷是否有RabbitListener注解
            RabbitListener rabbitListener = clazz.getAnnotation(RabbitListener.class);
            //類上有注解 就創(chuàng)建隊(duì)列
            if (ObjectUtil.isNotEmpty(rabbitListener)) {
                createQueue(rabbitListener);
            }
            //方法上有注解 就創(chuàng)建隊(duì)列
            for (Method method : methods) {
                RabbitListener methodRabbitListener = method.getAnnotation(RabbitListener.class);
                if (ObjectUtil.isNotEmpty(methodRabbitListener)) {
                    createQueue(methodRabbitListener);
                }
            }
        }
    }

    /**
     * 初始化隊(duì)列
     *
     * @param rabbitListener
     */
    private void createQueue(RabbitListener rabbitListener) {
        String[] queues = rabbitListener.queues();
        //創(chuàng)建交換機(jī)
        DirectExchange directExchange = createExchange(DelayExchangeBuilder.DELAY_EXCHANGE);
        rabbitAdmin.declareExchange(directExchange);
        //創(chuàng)建隊(duì)列
        if (ObjectUtil.isNotEmpty(queues)) {
            for (String queueName : queues) {
                Properties result = rabbitAdmin.getQueueProperties(queueName);
                if (ObjectUtil.isEmpty(result)) {
                    Queue queue = new Queue(queueName);
                    addQueue(queue);
                    Binding binding = BindingBuilder.bind(queue).to(directExchange).with(queueName);
                    rabbitAdmin.declareBinding(binding);
                    log.info("創(chuàng)建隊(duì)列:" + queueName);
                }else{
                    log.info("已有隊(duì)列:" + queueName);
                }
            }
        }
    }

3.RabbitMqConfig

為消息隊(duì)列的常用配置方式。這里不多描述。

4.event

這個(gè)包主要是為使用mq發(fā)送消息使用,多類別的消息會(huì)實(shí)現(xiàn)JeecgBusEventHandler類,而BaseApplicationEvent通過(guò)消息類型傳入的不同的參數(shù)選擇合適的業(yè)務(wù)類發(fā)送消息。

5.DelayExchangeBuilder

為延時(shí)隊(duì)列的交換機(jī)聲明與綁定。

2.jeecg-boot-starter-lock

1.如何使用分布式鎖

使用時(shí)有兩種方式,一種是使用注解方式,一種是使用redisson提供的API。

@Scheduled(cron = "0/5 * * * * ?")
    @JLock(lockKey = CloudConstant.REDISSON_DEMO_LOCK_KEY1)
    public void execute() throws InterruptedException {
        log.info("執(zhí)行execute任務(wù)開始,休眠三秒");
        Thread.sleep(3000);
        System.out.println("=======================業(yè)務(wù)邏輯1=============================");
        Map map = new BaseMap();
        map.put("orderId", "BJ0001");
        rabbitMqClient.sendMessage(CloudConstant.MQ_JEECG_PLACE_ORDER, map);
        //延遲10秒發(fā)送
        map.put("orderId", "NJ0002");
        rabbitMqClient.sendMessage(CloudConstant.MQ_JEECG_PLACE_ORDER, map, 10000);
        log.info("execute任務(wù)結(jié)束,休眠三秒");
    }

    public DemoLockTest() {
    }

    /**
     * 測(cè)試分布式鎖【編碼方式】
     */
    //@Scheduled(cron = "0/5 * * * * ?")
    public void execute2() throws InterruptedException {
        if (redissonLock.tryLock(CloudConstant.REDISSON_DEMO_LOCK_KEY2, -1, 6000)) {
            log.info("執(zhí)行任務(wù)execute2開始,休眠十秒");
            Thread.sleep(10000);
            System.out.println("=======================業(yè)務(wù)邏輯2=============================");
            log.info("定時(shí)execute2結(jié)束,休眠十秒");

            redissonLock.unlock(CloudConstant.REDISSON_DEMO_LOCK_KEY2);
        } else {
            log.info("execute2獲取鎖失敗");
        }
    }

2.RepeatSubmitAspect

通過(guò)公平鎖判斷是否是多次點(diǎn)擊按鈕。

    @Around("pointCut(jRepeat)")
    public Object repeatSubmit(ProceedingJoinPoint joinPoint,JRepeat jRepeat) throws Throwable {
        String[] parameterNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(((MethodSignature) joinPoint.getSignature()).getMethod());
        if (Objects.nonNull(jRepeat)) {
            // 獲取參數(shù)
            Object[] args = joinPoint.getArgs();
            // 進(jìn)行一些參數(shù)的處理,比如獲取訂單號(hào),操作人id等
            StringBuffer lockKeyBuffer = new StringBuffer();
            String key =getValueBySpEL(jRepeat.lockKey(), parameterNames, args,"RepeatSubmit").get(0);
            // 公平加鎖,lockTime后鎖自動(dòng)釋放
            boolean isLocked = false;
            try {
                isLocked = redissonLockClient.fairLock(key, TimeUnit.SECONDS, jRepeat.lockTime());
                // 如果成功獲取到鎖就繼續(xù)執(zhí)行
                if (isLocked) {
                    // 執(zhí)行進(jìn)程
                    return joinPoint.proceed();
                } else {
                    // 未獲取到鎖
                    throw new Exception("請(qǐng)勿重復(fù)提交");
                }
            } finally {
                // 如果鎖還存在,在方法執(zhí)行完成后,釋放鎖
                if (isLocked) {
                    redissonLockClient.unlock(key);
                }
            }
        }
        return joinPoint.proceed();
    }

3.DistributedLockHandler

該類主要是jLock的切面類,通過(guò)jLock注解參數(shù),判斷需要加鎖的類型,同時(shí)加鎖的方法也不相同。

    //jLock切面,進(jìn)行加鎖
    @SneakyThrows
    @Around("@annotation(jLock)")
    public Object around(ProceedingJoinPoint joinPoint, JLock jLock) {
        Object obj = null;
        log.info("進(jìn)入RedisLock環(huán)繞通知...");
        RLock rLock = getLock(joinPoint, jLock);
        boolean res = false;
        //獲取超時(shí)時(shí)間
        long expireSeconds = jLock.expireSeconds();
        //等待多久,n秒內(nèi)獲取不到鎖,則直接返回
        long waitTime = jLock.waitTime();
        //執(zhí)行aop
        if (rLock != null) {
            try {
                if (waitTime == -1) {
                    res = true;
                    //一直等待加鎖
                    rLock.lock(expireSeconds, TimeUnit.MILLISECONDS);
                } else {
                    res = rLock.tryLock(waitTime, expireSeconds, TimeUnit.MILLISECONDS);
                }
                if (res) {
                    obj = joinPoint.proceed();
                } else {
                    log.error("獲取鎖異常");
                }
            } finally {
                if (res) {
                    rLock.unlock();
                }
            }
        }
        log.info("結(jié)束RedisLock環(huán)繞通知...");
        return obj;
    }

    //通過(guò)參數(shù)判斷加鎖類型
    @SneakyThrows
    private RLock getLock(ProceedingJoinPoint joinPoint, JLock jLock) {
        //獲取key
        String[] keys = jLock.lockKey();
        if (keys.length == 0) {
            throw new RuntimeException("keys不能為空");
        }
        //獲取參數(shù)
        String[] parameterNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(((MethodSignature) joinPoint.getSignature()).getMethod());
        Object[] args = joinPoint.getArgs();
        LockModel lockModel = jLock.lockModel();
        if (!lockModel.equals(LockModel.MULTIPLE) && !lockModel.equals(LockModel.REDLOCK) && keys.length > 1) {
            throw new RuntimeException("參數(shù)有多個(gè),鎖模式為->" + lockModel.name() + ".無(wú)法鎖定");
        }
        RLock rLock = null;
        String keyConstant = jLock.keyConstant();
        //判斷鎖類型
        if (lockModel.equals(LockModel.AUTO)) {
            if (keys.length > 1) {
                lockModel = LockModel.REDLOCK;
            } else {
                lockModel = LockModel.REENTRANT;
            }
        }
        //根據(jù)不同的鎖類型執(zhí)行不同的加鎖方式
        switch (lockModel) {
            case FAIR:
                rLock = redissonClient.getFairLock(getValueBySpEL(keys[0], parameterNames, args, keyConstant).get(0));
                break;
            case REDLOCK:
                List<RLock> rLocks = new ArrayList<>();
                for (String key : keys) {
                    List<String> valueBySpEL = getValueBySpEL(key, parameterNames, args, keyConstant);
                    for (String s : valueBySpEL) {
                        rLocks.add(redissonClient.getLock(s));
                    }
                }
                RLock[] locks = new RLock[rLocks.size()];
                int index = 0;
                for (RLock r : rLocks) {
                    locks[index++] = r;
                }
                rLock = new RedissonRedLock(locks);
                break;
            case MULTIPLE:
                rLocks = new ArrayList<>();
                for (String key : keys) {
                    List<String> valueBySpEL = getValueBySpEL(key, parameterNames, args, keyConstant);
                    for (String s : valueBySpEL) {
                        rLocks.add(redissonClient.getLock(s));
                    }
                }
                locks = new RLock[rLocks.size()];
                index = 0;
                for (RLock r : rLocks) {
                    locks[index++] = r;
                }
                rLock = new RedissonMultiLock(locks);
                break;
            case REENTRANT:
                List<String> valueBySpEL = getValueBySpEL(keys[0], parameterNames, args, keyConstant);
                //如果spel表達(dá)式是數(shù)組或者LIST 則使用紅鎖
                if (valueBySpEL.size() == 1) {
                    rLock = redissonClient.getLock(valueBySpEL.get(0));
                } else {
                    locks = new RLock[valueBySpEL.size()];
                    index = 0;
                    for (String s : valueBySpEL) {
                        locks[index++] = redissonClient.getLock(s);
                    }
                    rLock = new RedissonRedLock(locks);
                }
                break;
            case READ:
                rLock = redissonClient.getReadWriteLock(getValueBySpEL(keys[0], parameterNames, args, keyConstant).get(0)).readLock();
                break;
            case WRITE:
                rLock = redissonClient.getReadWriteLock(getValueBySpEL(keys[0], parameterNames, args, keyConstant).get(0)).writeLock();
                break;
        }
        return rLock;
    }

4.RedissonLockClient

redisson客戶端,提供了一大波方法,請(qǐng)自行查看。

public class RedissonLockClient {

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 獲取鎖
     */
    public RLock getLock(String lockKey) {
        return redissonClient.getLock(lockKey);
    }

    /**
     * 加鎖操作
     *
     * @return boolean
     */
    public boolean tryLock(String lockName, long expireSeconds) {
        return tryLock(lockName, 0, expireSeconds);
    }
    .
    .
    .

5.core包

主要通過(guò)application.yml配置文件獲取redis連接類型,通過(guò)根據(jù)該參數(shù)動(dòng)態(tài)的選擇策略類,連接redis。

public class RedissonManager {
    public Redisson getRedisson() {
        return redisson;
    }

    //Redisson連接方式配置工廠
    static class RedissonConfigFactory {

        private RedissonConfigFactory() {
        }

        private static volatile RedissonConfigFactory factory = null;

        public static RedissonConfigFactory getInstance() {
            if (factory == null) {
                synchronized (Object.class) {
                    if (factory == null) {
                        factory = new RedissonConfigFactory();
                    }
                }
            }
            return factory;
        }


        //根據(jù)連接類型創(chuàng)建連接方式的配置
        Config createConfig(RedissonProperties redissonProperties) {
            Preconditions.checkNotNull(redissonProperties);
            Preconditions.checkNotNull(redissonProperties.getAddress(), "redis地址未配置");
            RedisConnectionType connectionType = redissonProperties.getType();
            // 聲明連接方式
            RedissonConfigStrategy redissonConfigStrategy;
            if (connectionType.equals(RedisConnectionType.SENTINEL)) {
                redissonConfigStrategy = new SentinelRedissonConfigStrategyImpl();
            } else if (connectionType.equals(RedisConnectionType.CLUSTER)) {
                redissonConfigStrategy = new ClusterRedissonConfigStrategyImpl();
            } else if (connectionType.equals(RedisConnectionType.MASTERSLAVE)) {
                redissonConfigStrategy = new MasterslaveRedissonConfigStrategyImpl();
            } else {
                redissonConfigStrategy = new StandaloneRedissonConfigStrategyImpl();
            }
            Preconditions.checkNotNull(redissonConfigStrategy, "連接方式創(chuàng)建異常");

            return redissonConfigStrategy.createRedissonConfig(redissonProperties);
        }
    }
}

//策略實(shí)現(xiàn),此類是指定redis的連接方式是哨兵。
public class SentinelRedissonConfigStrategyImpl implements RedissonConfigStrategy {

	@Override
	public Config createRedissonConfig(RedissonProperties redissonProperties) {
		Config config = new Config();
		try {
			String address = redissonProperties.getAddress();
			String password = redissonProperties.getPassword();
			int database = redissonProperties.getDatabase();
			String[] addrTokens = address.split(",");
			String sentinelAliasName = addrTokens[0];
			// 設(shè)置redis配置文件sentinel.conf配置的sentinel別名
			config.useSentinelServers().setMasterName(sentinelAliasName);
			config.useSentinelServers().setDatabase(database);
			if (StringUtils.isNotBlank(password)) {
				config.useSentinelServers().setPassword(password);
			}
			// 設(shè)置哨兵節(jié)點(diǎn)的服務(wù)IP和端口
			for (int i = 1; i < addrTokens.length; i++) {
				config.useSentinelServers().addSentinelAddress(GlobalConstant.REDIS_CONNECTION_PREFIX+ addrTokens[i]);
			}
			log.info("初始化哨兵方式Config,redisAddress:" + address);
		} catch (Exception e) {
			log.error("哨兵Redisson初始化錯(cuò)誤", e);
			e.printStackTrace();
		}
		return config;
	}
}


6.jeecg-cloud-module

這里詳細(xì)的說(shuō)一下jeecg-cloud-gateway,因?yàn)槠渌亩际情_源項(xiàng)目沒(méi)下載即用。

jeecg-cloud-system-start為封裝start的使用方法,上文已經(jīng)介紹了。

1.jeecg-cloud-gateway

1.GatewayRoutersConfiguration

當(dāng)固定的幾個(gè)路由,有特殊化的執(zhí)行方法。

2.RateLimiterConfiguration

主要配置限流,與application.yml一起使用,下文配置含義是,發(fā)送過(guò)來(lái)的請(qǐng)求只能容納redis-rate-limiter.burstCapacity的配置(3次)多余的會(huì)全部丟棄(限流),每秒消費(fèi)redis-rate-limiter.replenishRate(1次)。

3.FallbackController

熔斷的執(zhí)行方法。

4.GlobalAccessTokenFilter

全局?jǐn)r截器,在調(diào)用其他服務(wù)時(shí),將用戶信息放在請(qǐng)求頭中。

5.SentinelFilterContextConfig

使Sentinel鏈路流控模式生效,固定寫法。

6.HystrixFallbackHandler、SentinelBlockRequestHandler

在降級(jí)/限流時(shí),將異常信息轉(zhuǎn)換成json返回給前臺(tái)。

7.LoderRouderHandler

動(dòng)態(tài)刷新路由。

8.MySwaggerResourceProvider、SwaggerResourceController

將swagger地址統(tǒng)一管理起來(lái)

9.DynamicRouteLoader、DynamicRouteService

DynamicRouteLoader:通過(guò)application.yml判斷從nacos/redis中獲取路由信息,并實(shí)現(xiàn)動(dòng)態(tài)的加載。
DynamicRouteService:為底層處理路由的API。

四.總體感想

文章到這里差不多就接近尾聲了,大多數(shù)功能附帶著代碼都講述了一遍。在功能上來(lái)說(shuō),jeecg提供了很多常用功能,如rabbitMq封裝、積木報(bào)表、代碼生成器等。這些在日常工作中有很大的概率碰上,如果有以上需求,可以來(lái)框架中直接復(fù)制粘貼即可。

但是在格式規(guī)范上,如出入?yún)⒌囊?guī)范,代碼的寫法,代碼的格式化等方面,并不是特別統(tǒng)一,且沒(méi)有嚴(yán)格規(guī)范。總體來(lái)說(shuō)非常適合做私活與畢業(yè)設(shè)計(jì),同時(shí)也是最早一批開源的前后端項(xiàng)目腳手架,爆贊。

到此這篇關(guān)于java開源項(xiàng)目jeecgboot的文章就介紹到這了,更多相關(guān)java開源項(xiàng)目jeecgboot內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Springboot ApplicationRunner的使用解讀

    Springboot ApplicationRunner的使用解讀

    這篇文章主要介紹了Springboot ApplicationRunner的使用解讀,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-05-05
  • idea的使用之關(guān)于tomcat熱部署的教程

    idea的使用之關(guān)于tomcat熱部署的教程

    這篇文章主要介紹了idea的使用之關(guān)于tomcat熱部署的教程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-09-09
  • Java實(shí)現(xiàn)掃雷游戲詳細(xì)代碼講解

    Java實(shí)現(xiàn)掃雷游戲詳細(xì)代碼講解

    windows自帶的游戲《掃雷》是陪伴了無(wú)數(shù)人的經(jīng)典游戲,本文將利用Java語(yǔ)言實(shí)現(xiàn)這一經(jīng)典的游戲,文中的示例代碼講解詳細(xì),感興趣的可以學(xué)習(xí)一下
    2022-05-05
  • SpringBoot之RabbitMQ的使用方法

    SpringBoot之RabbitMQ的使用方法

    這篇文章主要介紹了SpringBoot之RabbitMQ的使用方法,詳細(xì)的介紹了2種模式,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-12-12
  • 微信小程序調(diào)用微信登陸獲取openid及java做為服務(wù)端示例

    微信小程序調(diào)用微信登陸獲取openid及java做為服務(wù)端示例

    這篇文章主要介紹了微信小程序調(diào)用微信登陸獲取openid及java做為服務(wù)端示例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-01-01
  • Java并發(fā)系列之CyclicBarrier源碼分析

    Java并發(fā)系列之CyclicBarrier源碼分析

    這篇文章主要為大家詳細(xì)分析了Java并發(fā)系列之CyclicBarrier源碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-03-03
  • AsyncHttpClient KeepAliveStrategy源碼流程解讀

    AsyncHttpClient KeepAliveStrategy源碼流程解讀

    這篇文章主要為大家介紹了AsyncHttpClient KeepAliveStrategy源碼流程解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12
  • 利用POI生成EXCEL文件的方法實(shí)例

    利用POI生成EXCEL文件的方法實(shí)例

    Apache POI 是用Java編寫的免費(fèi)開源的跨平臺(tái)的 Java API,Apache POI提供API給Java程式對(duì)Microsoft Office格式檔案讀和寫的功能,下面這篇文章主要給大家介紹了關(guān)于利用POI生成EXCEL文件的相關(guān)資料,需要的朋友可以參考下
    2018-07-07
  • Spring boot 連接多數(shù)據(jù)源過(guò)程詳解

    Spring boot 連接多數(shù)據(jù)源過(guò)程詳解

    這篇文章主要介紹了Spring boot 連接多數(shù)據(jù)源過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-08-08
  • 關(guān)于eclipse中運(yùn)行tomcat提示端口被占用的4種解決

    關(guān)于eclipse中運(yùn)行tomcat提示端口被占用的4種解決

    這篇文章主要介紹了關(guān)于eclipse中運(yùn)行tomcat提示端口被占用的4種解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-01-01

最新評(píng)論