{"id":14977525,"url":"https://github.com/melthaw/spring-backend-boilerplate","last_synced_at":"2025-08-21T15:31:21.669Z","repository":{"id":23115267,"uuid":"98194011","full_name":"melthaw/spring-backend-boilerplate","owner":"melthaw","description":"The modularized backend boilerplate based on Spring Boot Framework, easy to get started and add your business part.","archived":false,"fork":false,"pushed_at":"2022-01-11T09:20:04.000Z","size":29831,"stargazers_count":163,"open_issues_count":0,"forks_count":30,"subscribers_count":6,"default_branch":"master","last_synced_at":"2024-12-06T10:23:06.550Z","etag":null,"topics":["audit","backend","boilerplate","gradle","gridfs","java","rbac","sms","spring-boot","spring-boot-starter","spring-data","spring-framework","spring-mvc","spring-security","storage"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/melthaw.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":"audit/impl/build.gradle","citation":null,"codeowners":null,"security":"security/core/build.gradle","support":null}},"created_at":"2017-07-24T13:31:40.000Z","updated_at":"2024-06-15T01:46:16.000Z","dependencies_parsed_at":"2022-08-07T10:16:45.701Z","dependency_job_id":null,"html_url":"https://github.com/melthaw/spring-backend-boilerplate","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/melthaw%2Fspring-backend-boilerplate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/melthaw%2Fspring-backend-boilerplate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/melthaw%2Fspring-backend-boilerplate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/melthaw%2Fspring-backend-boilerplate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/melthaw","download_url":"https://codeload.github.com/melthaw/spring-backend-boilerplate/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230520391,"owners_count":18238948,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["audit","backend","boilerplate","gradle","gridfs","java","rbac","sms","spring-boot","spring-boot-starter","spring-data","spring-framework","spring-mvc","spring-security","storage"],"created_at":"2024-09-24T13:55:49.404Z","updated_at":"2024-12-20T01:14:33.708Z","avatar_url":"https://github.com/melthaw.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Overview\n\nThe quick development boilerplate based on Spring (Boot) Framework which covers the general case of Java backend application\nsomething like Account Module, Security Foundation, Audit System, File Upload/Download, Message Notification, Role Based Access Control etc.\n\nAnd we also provide the CRUD sample to show how manage the item list , add new one item , update it or remove it.  \n\nWe hope this boilerplate can help the users to focus on their business part . Before that ,  we will explain how it is designed and implemented.\n\n* [用户使用指南-中文版](./README.zh_CN.md)\n\n# Get Started\n\n\u003e Please make sure the Java 8, Gradle 2.x and Mongodb are installed on your development machine.\n\nWe support Spring Cloud now, here is the [User Guide - Spring Cloud Support](./cloud/README.md)\n\n## Build\n\nBuild using `Gradle`\n\n```\ngradle clean build\n```\n\n## Start up - by Docker\n\n```\ncd openapi\ndocker-compose build\ndocker-compose up -d\n```\n\n## Start up - by Manual\n\n### Start API Server\n  \nAnd here is the minimized application.properties to start the boilerplate.\n  \n```ini\napp.name=spring-backend-boilerplate\napp.description=spring-backend-boilerplate\n\nin.clouthink.daas.sbb.account.password.salt=@account.sbb.daas.clouthink.in\nin.clouthink.daas.sbb.account.administrator.email=changeit@example.com\nin.clouthink.daas.sbb.account.administrator.username=administrator\nin.clouthink.daas.sbb.account.administrator.cellphone=13000000000\nin.clouthink.daas.sbb.account.administrator.password=Please_change_the_pwd\n\nin.clouthink.daas.sbb.setting.system.name=spring-backend-boilerplate\nin.clouthink.daas.sbb.setting.system.contactEmail=support-team@example.com\nin.clouthink.daas.sbb.setting.system.contactPhone=13000000000\n\nlogging.file=/var/sbb/log/server.log\nlogging.level.*=INFO\nlogging.level.in.clouthink.daas=DEBUG\n\nserver.port=8081\nserver.address=127.0.0.1\nserver.session-timeout=360000 \n\nspring.mvc.date-format=yyyy-MM-dd\nspring.mvc.favicon.enabled=false\n\nmultipart.enabled=true\nmultipart.max-file-size=20Mb\nmultipart.max-request-size=20Mb\n\nspring.http.encoding.charset=UTF-8\nspring.http.encoding.enabled=true\nspring.http.encoding.force=true\n\nspring.jackson.date-format=yyyy-MM-dd'T'HH:mm:ss.sss'Z'\n\nspring.data.mongodb.uri=mongodb://localhost:27017/spring-backend-boilerplate\n\n```  \n\nThen we can start it with\n\n```sh\n\u003e cd openapi/server\n\u003e gradle clean bootRun  -PjvmArgs=\"-Dspring.config.location=the_full_path_of_the_application.properties\"\n```\n\n## Start ApiDoc Server\n\nAnd here is the minimized application.properties to start the boilerplate. \n\n```ini\napp.name=spring-backend-boilerplate-api-doc\napp.description=spring backend boilerplate api doc\n\nserver.port=8082\nserver.address=127.0.0.1\nserver.session-timeout=360000\n```\n\nThen we can start it with\n\n```sh\n\u003e cd openapi/doc\n\u003e gradle clean bootRun  -PjvmArgs=\"-Dspring.config.location=the_full_path_of_the_application.properties\"\n```\n\nAfter the swagger doc server booted, open browser and visit the api doc at\n\n```\nhttp://127.0.0.1:8082/swagger-ui.html\n```\n\n# Features\n\n## Modularization\n\nFirst we design a modularized system (Thanks Spring Boot and Gradle),\nour goal is to simple add or remove one module without changing the Application , except the foundational modules.\n\nAll these come to reality are belongs to the features of Spring Boot Starter.\n\n* Provide the auto configuration for each module.\n* Tell spring how to enable the auto configuration.\n\n### Example\n\nMessage module for example\n\nAuto Configuration\n\n```\n@Configuration\n@Import({MockSmsModuleConfiguration.class, SmsHistoryModuleConfiguration.class})\npublic class DummySmsRestModuleConfiguration {\n\n}\n```\n\nStarter by spring.factories\n\n```ini\n#message/sms/starter/src/main/resources/META-INF/spring.factories\norg.springframework.boot.autoconfigure.EnableAutoConfiguration=\\\nin.clouthink.daas.sbb.sms.DummySmsRestModuleConfiguration\n```\n\n### Practise\n\nRemove the message module in the boilerplate won't break the application, because the message is event-driven ,\nThe other modules dispatch event to the event bus, the event bus simply discard it if the message module not found. \n\n```gradle\n//openapi/server/build.gradle\ndependencies {\n\n    ...\n    compile project(':sample/setting/starter')\n\n    compile project(':message/sms/starter')\n\n    compile project(':storage/starter')\n    ...\n}\n\n```\n\nSimply remove the module configuration from the startup script.\n\n```gradle\n//openapi/server/build.gradle\ndependencies {\n\n    ...\n    compile project(':sample/setting/starter')\n\n    //after\n    //compile project(':message/sms/starter')\n\n    compile project(':storage/starter')\n    ...\n}\n```\n\n### Without Starter\n\nAnd we also force the module convention that's separating the abstraction and implementation, then your can switch from one \nimplementation to another easily. \n\nFor example, we provide more than one implementation module for the storage abstraction (:storage/core).\n\n```java\n@Import({GridfsModuleConfiguration.class})\npublic class StorageRestModuleConfiguration {\n\n}\n\n```\nIf you want upload the file and save it to the local storage ( your runtime server's file system ), \njust import another one and replace it as follow\n\n```java\n@Import({LocalStorageModuleConfiguration.class})\npublic class StorageRestModuleConfiguration {\n\n}\n```\n\n\n## Security\n\n### Foundation\n\nSpring Security is a powerful security framework , it's easy to customize and extend . \nBased on the flexibility provided by Spring Security , we add more interesting feature to it like\nmulti-factor authentication, user device, audit and pluggable account system.\n\n\n### Context \n\nYes, Spring Security provides the context named `org.springframework.security.core.context.SecurityContext` and \nthe corresponding helper class `org.springframework.security.core.context.SecurityContextHolder`. \n\nWhy we need another one? \n\n```java\nAuthentication authentication = SecurityContextHolder.getContext().getAuthentication();\nif (authentication == null) {\n    throw some exception here?\n}\n\n```\n\nEvery time we retrieve the authentication , we must check the authentication , duplicated code everywhere.\n\nWhat we supposed is that an exception is thrown automatically if no authentication found in the context,  \nnot check it and throw it by manual.\n\nSo we provide one new generic interface in module(:security/core)\n\n```java\npublic interface SecurityContext\u003cT\u003e {\n\n\t/**\n\t * @return current user , or null if not authenticated\n\t */\n\tT currentUser();\n\n\t/**\n\t * @return the current user\n\t * @throw AuthenticationRequiredException if not authenticated\n\t */\n\tT requireUser();\n\n}\n```\n\nAnd we provide one helper named `SecurityContexts` to load the implementation. Here is the sample: \n\n```java\nUser user = (User)SecurityContexts.getContext().requireUser();\n```\n\nAs you see, `requireUser()` is invoked which means if no authenticated user found in the context, it will throw one AuthenticationRequiredException.\n\n\nThe `SecurityContexts` requires that the implementation must follow the Java SPI  Spec and provide it in the file as follow\n\n```\nMETA-INF/services/in.clouthink.daas.sbb.security.SecurityContext\n```\n\n### Authentication \u0026 Authorization\n\nFirst let's list the extension points what we implemented for Spring Security.\n\n`User`\n\n* org.springframework.security.authentication.AuthenticationProvider\n    * org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider\n* org.springframework.security.core.userdetails.User\n* org.springframework.security.core.userdetails.UserDetailsService\n\n`Login \u0026 Logout`\n\n* org.springframework.security.web.AuthenticationEntryPoint\n* org.springframework.security.web.authentication.AuthenticationFailureHandler\n* org.springframework.security.web.authentication.AuthenticationSuccessHandler\n* org.springframework.security.web.authentication.logout.LogoutSuccessHandler\n\n`Access`\n\n* org.springframework.security.web.access.AccessDeniedHandler\n    * org.springframework.security.web.access.AccessDeniedHandlerImpl\n* org.springframework.security.access.expression.SecurityExpressionHandler\n    * org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler\n* org.springframework.security.web.access.expression.WebSecurityExpressionRoot\n\n\nThe backend is designed as a restful service provider, \nso the security foundation must has the ability to process the restful request and response , \nnot only the web page request and response.\n\nAnd our implementations are listed as follow: \n\n`User`\n* in.clouthink.daas.sbb.security.impl.spring.UserDetailsAuthenticationProviderImpl\n* in.clouthink.daas.sbb.security.impl.spring.UserDetails\n* in.clouthink.daas.sbb.security.impl.spring.UserDetailsServiceImpl\n\n`Login \u0026 Logout`\n* in.clouthink.daas.sbb.security.impl.spring.rest.AuthenticationEntryPointRestImpl\n* in.clouthink.daas.sbb.security.impl.spring.rest.AuthenticationFailureHandlerRestImpl\n* in.clouthink.daas.sbb.security.impl.spring.rest.AuthenticationSuccessHandlerRestImpl\n* in.clouthink.daas.sbb.security.impl.spring.rest.LogoutSuccessHandlerRestImpl\n\n`Access`\n* in.clouthink.daas.sbb.security.impl.spring.rest.AccessDeniedHandlerRestImpl\n* in.clouthink.daas.sbb.rbac.impl.spring.security.RbacWebSecurityExpressionHandler\n* in.clouthink.daas.sbb.rbac.impl.spring.security.RbacWebSecurityExpressionRoot\n\n#### How to configure Spring Security \n\nPlease refer to `in.clouthink.daas.sbb.openapi.OpenApiSecurityConfigurer`. \n\nFirst , export the Spring Security extension points implementation. \n\n```java\n\n\t@Bean\n\tpublic AuthenticationProvider authenticationProvider() {\n\t\treturn new UserDetailsAuthenticationProviderImpl();\n\t}\n\n\t@Bean\n\tpublic UserDetailsService userDetailsService() {\n\t\treturn new UserDetailsServiceImpl();\n\t}\n\n\t@Bean\n\tpublic AuthenticationSuccessHandler authenticationSuccessHandlerImpl() {\n\t\treturn new AuthenticationSuccessHandlerRestImpl();\n\t}\n\n\t@Bean\n\tpublic AuthenticationFailureHandler authenticationFailureHandlerImpl() {\n\t\treturn new AuthenticationFailureHandlerRestImpl();\n\t}\n\n\t@Bean\n\tpublic AccessDeniedHandler accessDeniedHandlerImpl() {\n\t\treturn new AccessDeniedHandlerRestImpl();\n\t}\n\n\t@Bean\n\tpublic LogoutSuccessHandler logoutSuccessHandlerImpl() {\n\t\treturn new LogoutSuccessHandlerRestImpl();\n\t}\n\n\t@Bean\n\tpublic AuthenticationEntryPoint authenticationEntryPointImpl() {\n\t\treturn new AuthenticationEntryPointRestImpl();\n\t}\n\n\t@Bean\n\tpublic AccessDecisionManager accessDecisionManager() {\n\t\tList\u003cAccessDecisionVoter\u003c? extends Object\u003e\u003e decisionVoters = new ArrayList\u003c\u003e();\n\t\tdecisionVoters.add(new RoleVoter());\n\t\tdecisionVoters.add(new AuthenticatedVoter());\n\t\tdecisionVoters.add(webExpressionVoter());\n\t\treturn new AffirmativeBased(decisionVoters);\n\t}\n\n\t@Bean\n\tpublic WebExpressionVoter webExpressionVoter() {\n\t\tWebExpressionVoter result = new WebExpressionVoter();\n\t\tresult.setExpressionHandler(rbacWebSecurityExpressionHandler());\n\t\treturn result;\n\t}\n\n\t@Bean\n\tpublic SecurityExpressionHandler rbacWebSecurityExpressionHandler() {\n\t\treturn new RbacWebSecurityExpressionHandler();\n\t}\n\n\t@Override\n\tpublic void configure(AuthenticationManagerBuilder auth) throws Exception {\n\t\tauth.authenticationProvider(authenticationProvider())\n\t\t\t.eraseCredentials(true)\n\t\t\t.userDetailsService(userDetailsService());\n\t}\n\n```\n\nThen configure the authentication part\n\n```java\n\tprivate void configLogin(HttpSecurity http) throws Exception {\n\t\thttp.csrf()\n\t\t\t.disable()\n\t\t\t.formLogin()\n\t\t\t.loginPage(\"/login\")\n\t\t\t.permitAll()\n\t\t\t.successHandler(authenticationSuccessHandlerImpl())\n\t\t\t.failureHandler(authenticationFailureHandlerImpl())\n\t\t\t.loginProcessingUrl(\"/login\")\n\t\t\t.usernameParameter(\"username\")\n\t\t\t.passwordParameter(\"password\")\n\t\t\t.and()\n\t\t\t.logout()\n\t\t\t.logoutUrl(\"/logout\")\n\t\t\t.logoutSuccessHandler(logoutSuccessHandlerImpl())\n\t\t\t.invalidateHttpSession(true)\n\t\t\t.deleteCookies(\"JSESSIONID\")\n\t\t\t.permitAll()\n\t\t\t.and()\n\t\t\t.rememberMe()\n\t\t\t.key(\"PLEASE_CHANGE_THIS\");\n\t}\n\n```\n\nFinally configure the authorization part\n\n```java\n\tprivate void configAccess(HttpSecurity http) throws Exception {\n\t\thttp.headers().frameOptions().disable();\n\n\t\thttp.authorizeRequests()\n\t\t\t.accessDecisionManager(accessDecisionManager())\n\t\t\t.antMatchers(\"/\", \"/static/**\", \"/login**\", \"/guest/**\")\n\t\t\t.permitAll()\n\t\t\t.antMatchers(\"/api/shared/**\")\n\t\t\t.hasRole(\"USER\")\n\t\t\t.antMatchers(\"/api/_devops_/**\")\n\t\t\t.hasRole(\"ADMIN\")\n\t\t\t.antMatchers(\"/api/**\")\n\t\t\t.access(\"passRbacCheck\")\n\t\t\t.and()\n\t\t\t.exceptionHandling()\n\t\t\t.authenticationEntryPoint(authenticationEntryPointImpl())\n\t\t\t.accessDeniedHandler(accessDeniedHandlerImpl());\n\t}\n\n```\n\n\n## Audit\n\nThe [daas-audit](https://github.com/melthaw/spring-mvc-audit) is a simple and quick audit abstraction lib for spring mvc http request.\nPlease go https://github.com/melthaw/spring-mvc-audit to get more detail about it. Here we only explain what we extended and customized.  \n\nThe module(:audit/impl) implements the daas-audit's AuditEvent SPI including:\n \n* in.clouthink.daas.audit.core.MutableAuditEvent\n    * in.clouthink.daas.sbb.audit.domain.model.AuditEvent\n* in.clouthink.daas.audit.spi.AuditEventPersister\n    * in.clouthink.daas.sbb.audit.spiImpl.AuditEventPersisterImpl\n\nAnd the login history is supported which is not covered in daas-audit.\n\n* in.clouthink.daas.sbb.audit.domain.model.AuthEvent\n* in.clouthink.daas.sbb.audit.service.AuthEventService\n\nHere is the configuration to enable the audit module\n\n```java\n@EnableAudit\npublic class SpringBootApplication extends SpringBootServletInitializer {\n\n\t@Bean\n\tpublic AuditEventPersister auditEventPersisterImpl() {\n\t\treturn new AuditEventPersisterImpl();\n\t}\n\n\t@Bean\n\tpublic AuditConfigurer auditConfigurer() {\n\t\treturn result -\u003e {\n\t\t\tresult.setSecurityContext(new SecurityContextAuditImpl());\n\t\t\tresult.setAuditEventPersister(auditEventPersisterImpl());\n\t\t\tresult.setErrorDetailRequired(true);\n\t\t};\n\t}\n\n    public static void main(String[] args) { \n        AuditRestModuleConfiguration.class,\n        ...\n        SpringBootApplication.class\n    }\n}\n```\n\n## File Storage\n\nThe [daas-fss](https://github.com/melthaw/spring-file-storage-service) is APIs which make storing the blob file easy and simple.\nPlease go https://github.com/melthaw/spring-file-storage-service to get more detail about it. Here we only explain what we extended and customized.  \n\nNow we provide three file storage implementation \n* aliyun oss (:storage/alioss)\n* mongodb gridfs (:storage/gridfs)\n* local file system (:storage/localfs)\n\nBecause different storage service stores the file in different system , the download url goes to different as well.\n\nHere is the abstraction we supplied to extend.\n\n* in.clouthink.daas.sbb.storage.spi.DownloadUrlProvider\n\nFor example, if you choose the :storage/localfs , the download url goes to:\n\n```java\npublic class LocalfsDownloadUrlProvider implements DownloadUrlProvider {\n\n\t@Autowired\n\tprivate LocalfsConfigureProperties localfsConfigureProperties;\n\n\t@Autowired\n\tprivate FileObjectService fileObjectService;\n\n\t@Override\n\tpublic String getDownloadUrl(String id) {\n\t\tFileObject fileObject = fileObjectService.findById(id);\n\t\tif (fileObject == null) {\n\t\t\tthrow new FileNotFoundException(id);\n\t\t}\n\n\t\treturn localfsConfigureProperties.getDowloadUrlPrefix() + fileObject.getFinalFilename();\n\t}\n\n}\n\n```\n\nHere is the configuration to enable the `Storage` module\n\n```java\npublic class SpringBootApplication extends SpringBootServletInitializer {\n\n    public static void main(String[] args) { \n        StorageModuleConfiguration.class,\n        ...\n        SpringBootApplication.class\n    }\n}\n```\n\n## Message\n\nThe [daas-edm](https://github.com/melthaw/spring-event-driven-message) is a lightweight event driven message framework based on reactor.\nPlease go https://github.com/melthaw/spring-event-driven-message to get more detail about it. Here we only explain what we extended and customized.\n\n\n### SMS\n\nIn the boilerplate we choose the [aliyun SMS](https://www.aliyun.com/product/sms) as example .\nIt's simple and easy to integrate your SMS provider , just implement the following interface.\n\n```\nin.clouthink.daas.edm.sms.SmsSender\n```\n\nWe also supply one dummy module(:message/sms/mock) which can be used in the development ENV. \n\n```java\n//for development\n@Import({SmsAliyunModuleConfiguration.class})\n//for production\n@Import({DummySmsModuleConfiguration.class})\n```\n\nOne more thing, the SMS history can be saved if you import the history module (:message/sms/history). \nBy default we enable the SMS history in the boilerplate, if you don't like it, simple remove the import part to disable this feature.\n\n```java\n@Configuration\n@Import({SmsHistoryModuleConfiguration.class})\npublic class SmsRestModuleConfiguration {\n\n}\n\n```\n\nHere is the configuration to enable the `Message` module\n\n```java\npublic class SpringBootApplication extends SpringBootServletInitializer {\n\n    public static void main(String[] args) { \n        SmsRestModuleConfiguration.class,\n        ...\n        SpringBootApplication.class\n    }\n}\n```\n\n# Configuration\n\n\n## application.properties\n\n### account\n\n```ini\nin.clouthink.daas.sbb.account.password.salt=\nin.clouthink.daas.sbb.account.administrator.email=\nin.clouthink.daas.sbb.account.administrator.username=\nin.clouthink.daas.sbb.account.administrator.cellphone=\nin.clouthink.daas.sbb.account.administrator.password=\n```\n### storage\n\n```ini\n#alioss\nin.clouthink.daas.sbb.storage.alioss.keyId=\nin.clouthink.daas.sbb.storage.alioss.secret=\nin.clouthink.daas.sbb.storage.alioss.ossDomain=\nin.clouthink.daas.sbb.storage.alioss.imgDomain=\nin.clouthink.daas.sbb.storage.alioss.defaultBucket=\nin.clouthink.daas.sbb.storage.alioss.buckets.key1=\nin.clouthink.daas.sbb.storage.alioss.buckets.key2=\n```\n\n### sms\n\n```ini\nin.clouthink.daas.sbb.sms.aliyun.area=\nin.clouthink.daas.sbb.sms.aliyun.accessKey=\nin.clouthink.daas.sbb.sms.aliyun.accessSecret=\nin.clouthink.daas.sbb.sms.aliyun.signature=\nin.clouthink.daas.sbb.sms.aliyun.smsEndpoint=\nin.clouthink.daas.sbb.sms.aliyun.templateId=\n```\n\n\n### setting\n\n```ini\nin.clouthink.daas.sbb.setting.system.name=\nin.clouthink.daas.sbb.setting.system.contactEmail=\nin.clouthink.daas.sbb.setting.system.contactPhone=\n```\n\n## resource \n\nThe resource is the term we called in RBAC, which is protected by authorization system. \nThe resource should be a rest endpoint,or the visible menu item in the GUI , even a Create button in a page.\n \nWe design a resource registry SPI as \n\n* in.clouthink.daas.sbb.rbac.spi.ResourceProvider\n\nAll the resource provider implementation only required to implement it as a spring bean. \nThe boilerplate scans and discovers it , and register the resources automatically.\n\nThe classic resource is the menu and action which are granted to the role and accessed control by role permission.\nWe design the pluggable menu \u0026 action module , and supply the annotation to make it easy to use.\n\nHere is the example\n\n```java\n@EnableMenu(pluginId = \"plugin:menu:sample\",\n\t\t\textensionPointId = Menus.ROOT_EXTENSION_POINT_ID,\n\t\t\tmenu = {@Menu(virtual = true,\n\t\t\t\t\t\t  code = \"menu:dashboard:sample\",\n\t\t\t\t\t\t  name = \"sample\",\n\t\t\t\t\t\t  order = 100,\n\t\t\t\t\t\t  metadata = {@Metadata(key = \"icon\", value = \"fa fa-gear\")},\n\t\t\t\t\t\t  extensionPoint = {@ExtensionPoint(id = \"extension:menu:sample\")}),\n\n\t\t\t\t\t@Menu(virtual = true,\n\t\t\t\t\t\t  code = \"menu:dashboard:system\",\n\t\t\t\t\t\t  name = \"system\",\n\t\t\t\t\t\t  order = 200,\n\t\t\t\t\t\t  metadata = {@Metadata(key = \"icon\", value = \"fa fa-gear\")},\n\t\t\t\t\t\t  extensionPoint = {@ExtensionPoint(id = \"extension:menu:system\")}),\n\n\t\t\t})\n```\n\nMore detail about the usage please check out the description of the Java file.\n\n# Sample - new business module \n\n`TODO`\n \n# Appendix - Development ENV\n \n## IDEA - how to import the project to IDEA IDE\n\n```sh\n\u003e gradle cleanIdea\n\u003e gradle idea\n```\n\n## IDEA - how to debug in IDEA IDE\n\nCreate new debug configuration (type of gradle), and pop it with following value.\n\nname | value\n-----|-----\nGradle Project | spring-backend-boilerplate:openapi:server\nTasks | clean bootRun\nVM Options | \u003ckeep it empty\u003e\nScript parameters | -PjvmArgs=\"-Dspring.config.location=/var/sbb/etc/openapi/application.properties\"\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmelthaw%2Fspring-backend-boilerplate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmelthaw%2Fspring-backend-boilerplate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmelthaw%2Fspring-backend-boilerplate/lists"}