solon架構(spring-boot未來最有效的競爭力)

  一、現在基本WEB的開發都是用spring-boot來做開發了,但是springboot存在很多弊端。體量大,啓動慢等。優點就是生態比較完整,文檔說明也比較多。

  二、solon架構,是我學習其他框架兼容時瞭解的,說說其區別之處。

  1)solon目前已經有一定規模了,有自己的生態圈了吧

  

  2)solon算的上新生態的開發框架了。

  優點:更快、更小、更簡單。(主要體現在啓動很快,目前基本在5S(不絕對,官網說5~10倍)以內,Qps10萬+。打包更小,官方說小1/2~1/10。內存更小。簡單主要是在開發上面)

  

  

  缺點:文檔相對較少,雖然後官方實例,但是要完整的用起來還是得自己研究。且存在BUG。

  3)與springboot的區別

  a、與 Springboot 的常用註解比較

Solon 2.2.0Springboot 2.7.8說明
@Inject * @Autowired 注入Bean(by type)
@Inject("name") @Qualifier+@Autowired 注入Bean(by name)
@Inject("${name}") @Value("${name}") + @ConfigurationProperties(prefix="name") 注入配置
@Singleton @Scope(“singleton”) 單例(Solon 默認是單例)
@Singleton(false) @Scope(“prototype”) 非單例
     
@Import @Import + @ComponentScan 配置組件導入或掃描(一般加在啓動類上)
@PropertySource @PropertySource 配置屬性源(一般加在啓動類上)
     
@Configuration @Configuration 配置類
@Bean @Bean 配置Bean
@Condition @ConditionalOnClass + @ConditionalOnProperty 配置條件
     
@Controller @Controller,@RestController 控制器類
@Remoting   遠程控制器類(即 Rpc 服務端)
@Mapping ... @RequestMapping,@GetMapping... 映射
@Param @RequestParam 請求參數
@Header @RequestHeader 請求頭
@Body @RequestBody 請求體
@Cookie @CookieValue 請求Cookie
     
@Component @Component 普通託管組件
@ProxyComponent @Service,@Dao,@Repository 代理託管組件
     
@TestPropertySource @TestPropertySource 配置測試屬性源
@TestRollback @TestRollback 執行測試回滾
     
LifecycleBean InitializingBean + DisposableBean 組件初始化和銷燬
     
Solon 2.2.0 Java EE(Java 11 後更名爲 Jakarta)  
LifecycleBean::start @PostConstruct 組件構造完成並注入後的初始化
LifecycleBean::stop @PreDestroy 組件銷燬

  

  b、重要的區別,Solon 不是基於 Servlet 的開發框架

   

  c、其他區別也比較多。這裏不一一說明。參考地址:https://solon.noear.org/article/compare-springboot

   三、開發部分說明:(這裏只有cloud相關的,單體的相對簡單)

  1)集羣註冊與發現,以nacos爲例。

        <dependency>
            <groupId>org.noear</groupId>
            <artifactId>nacos-solon-cloud-plugin</artifactId>
        </dependency>

  2)配置app.yml(測試只能在resources下),具體配置:https://solon.noear.org/article/148

solon:
  app:
    name: demo
    group: xbd
    namespace: public
  cloud:
    nacos:
      server: 127.0.0.1:8848

  3)rpc適用簡單,優點類似dubbo的調用方式,抽公共的接口就行。

        <dependency>
            <groupId>org.noear</groupId>
            <artifactId>solon-rpc</artifactId>
        </dependency>

  server

@Service
@Mapping("/user")
@Remoting
public class UserServiceImpl implements IUserService {

    @Tran
    public List<String> listUser() {
        List<String> users = new ArrayList<String>();
        for (int i = 0; i < 5; i++) {
            users.add("user" + i);
        }
        return users;
    }
}

  client

    @NamiClient(name = "demo", path = "/user", headers = ContentTypes.PROTOBUF)
    private IUserService userService;

  

   4)認證(jwt)

        <dependency>
            <groupId>org.noear</groupId>
            <artifactId>solon.auth</artifactId>
        </dependency>
        <dependency>
            <groupId>org.noear</groupId>
            <artifactId>solon.sessionstate.jwt</artifactId>
        </dependency>

  配置

server:
  port: 8673
  session:
    #超時配置。單位秒(可不配,默認:7200timeout: 86400
    state:
      #可共享域配置(可不配,默認當前服務域名;多系統共享時要配置)
      domain: "xbd.auth.com"
      jwt:
        #Jwt 令牌變量名;(可不配,默認:TOKEN)
        name: "Authorization"
        #Jwt 密鑰(使用 JwtUtils.createKey() 生成);(可不配,默認:xxx)
        secret: "QQRsCNRmPqZQtAo5ANqZ9OgG5N2mOqZl5D3i1VSDvJs="
        #Jwt 令牌前綴(可不配,默認:空)
        prefix: "Bearer"
        #Jwt 允許超時;(可不配,默認:true);false,則token一直有效
        allowExpire: true
        #Jwt 允許自動輸出;(可不配,默認:true);flase,則不向header 或 cookie 設置值(由用戶手動控制)
        allowAutoIssue: false
        #Jwt 允許使用Header傳遞;(可不配,默認:使用 Cookie 傳遞);true,則使用 header 傳遞
        allowUseHeader: true
@Controller
@Mapping("/auth")
public class AuthController {

    @Inject
    private IUserService userService;

    @Get
    @Mapping("/test")
    public Result test(Context context) {
        return Result.succeed(context.session("userId"));
    }

    @Post
    @Mapping("/login")
    public Result login(Context context, @Body JSONObject jsonObject) {
        String username = jsonObject.getString("username");
        String password = jsonObject.getString("password");
        boolean result = userService.checkUser(context, username, password);
        return result ? Result.succeed() : Result.failure("密碼錯誤!");
    }
}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Override
    public boolean checkUser(Context context, String username, String password) {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("username", username);
        User user = baseMapper.selectOne(wrapper);
        if (user == null) {
            throw new ApiException("用戶不存在");
        }
        boolean check = DigestUtil.bcryptCheck(password, user.getPassword());
        if (check) {
            context.sessionSet("userId", user.getId());
            context.sessionSet("username", user.getUsername());
            context.cookieSet("TOKEN", context.sessionState().sessionToken());
        }
        return check;
    }
}
@Configuration
public class AuthConfiguration {

    @Bean
    public AuthAdapter authAdapter() {
        return new AuthAdapter()
//            .loginUrl("/login") //設定登錄地址,未登錄時自動跳轉(如果不設定,則輸出401錯誤)
            .addRule(r -> r.include("/**").exclude("/auth/login").verifyLogined()) //添加規則
            .processor(new AuthProcessor() {
                @Override
                public boolean verifyIp(String ip) {
                    return false;
                }

                @Override
                public boolean verifyLogined() {
                    Context context = ContextUtil.current();
                    if (context.session("userId") != null) {
                        return true;
                    }
                    return false;
                }

                @Override
                public boolean verifyPath(String path, String method) {
                    return false;
                }

                @Override
                public boolean verifyPermissions(String[] permissions, Logical logical) {
                    return false;
                }

                @Override
                public boolean verifyRoles(String[] roles, Logical logical) {
                    return false;
                }
            }).failure((ctx, rst) -> {
                System.out.println(rst);
                ctx.output(JSONObject.toJSONString(rst));
            });
    }

}

   參考地址:https://solon.noear.org/article/59 https://solon.noear.org/article/132

  說實話這個認證太簡單了,二次校驗等等問題,相對於security真的太輕量級了。在結合redis緩存,基本可以最到登陸和登出的一些判定了。

  5)多數據源:地址:https://solon.noear.org/article/231

        <dependency>
            <groupId>org.noear</groupId>
            <artifactId>mybatis-plus-extension-solon-plugin</artifactId>
        </dependency>
@Configuration
public class DataSourceConfiguration {

    @Bean(name = "default", typed = true)
    public DataSource dataSource(@Inject("${datasource.default}") HikariDataSource dataSource){
        return dataSource;
    }
}

  不多說,應用就會了。

  6)網關(官網推薦nginx等三方產品):說實話這個就真的比較一般了。數據轉發出現亂碼的情況,改用其他的就正常。

        <dependency>
            <groupId>org.noear</groupId>
            <artifactId>solon-api</artifactId>
        </dependency>
        <!-- 適用httputils亂碼 -->
        <dependency>
            <groupId>org.noear</groupId>
            <artifactId>solon.cloud.httputils</artifactId>
        </dependency>

  

@Slf4j
@Component
@Mapping("/**")
public class RouterConfiguration extends Gateway {

    @Override
    protected void register() {
        //添加個前置處理
        before(ctx -> {
            String token = ctx.sessionState().sessionToken();
            System.out.println(token);
        });

        //添加缺省處理
        add(Any.class);
    }

    public static class Any {

        @Mapping
        public String any(Context ctx) throws Throwable {
            //檢測請求,並嘗試獲取二級接口服務名
            String serviceName = ctx.pathMap("/{serviceName}/**").get("serviceName");
            if (serviceName == null) {
                log.error(MessageFormat.format("{0} service not found", serviceName));
                throw new ApiException(500, "服務異常!");
            }

            //轉發請求
            String method = ctx.method();
            String server = LoadBalance.get(serviceName).getServer();
            if (server == null) {
                log.error(MessageFormat.format("{0} service not exception", serviceName));
                throw new ApiException(500, "服務異常!");
            }
            HttpRequest request = HttpUtil.createRequest(Method.valueOf(method), server + ctx.path().substring(StrUtil.indexOf(ctx.path(), '/', 2)));
            request.addHeaders(ctx.headerMap());
            request.header(CloudClient.trace().HEADER_TRACE_ID_NAME(), CloudClient.trace().getTraceId());
            request.body(ctx.body());
            HttpResponse response = request.execute();
            if (200 == response.getStatus()) {
                return response.body();
            } else {
                throw new ApiException(response.getStatus(), response.body());
            }

        }
    }

    @Override
    public void render(Object obj, Context context) throws Throwable {
        if (context.getRendered()) {
            return;
        }

        context.setRendered(true);

        Object result;
        if (obj instanceof ApiException) {
            ApiException exception = (ApiException) obj;
            result = exception.data();
        } else {
            //處理java bean數據(爲擴展新的)
            result = obj;
        }
        context.result = result;
        //如果想對輸出時間點做控制,可以不在這裏渲染(由後置處理進行渲染)
        context.render(context.result);
    }
}
public class ApiException extends DataThrowable {

    public ApiException() {
        super.data(Result.failure());
    }

    public ApiException(int code, String message) {
        super.data(Result.failure(code, message));
    }

    public ApiException(Throwable cause) {
        super(cause);
        super.data(Result.failure(cause.getMessage()));
    }

    public ApiException(String message) {
        super(message);
        super.data(Result.failure(message));
    }

    public ApiException(String message, Throwable cause) {
        super(message, cause);
        super.data(Result.failure(message));
    }


}

  四、開發的幾個重要部分,簡單搞了一下,其他的都有對應策略,這裏不細講了。

  官網地址:https://solon.noear.org/

  五、總體來說這套框架還是非常優秀的。但是還是存在很多bug吧,個人準備弄着玩。

  demo地址:https://gitee.com/lilin409546297/solon-demo

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章