Skip to main content

grails-developer

Comprehensive guide for Grails development, covering web applications, REST APIs, GORM, controllers, services, views, plugins, and testing with Spock and Geb

来源信息

仓库
apache/grails-core
最近来源活动
2026年9月29日 12:42
检测到的 SKILL.md 语言
英语
星标
2,937
分支
977

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
grails-developer
description
Comprehensive guide for Grails development, covering web applications, REST APIs, GORM, controllers, services, views, plugins, and testing with Spock and Geb
license
Apache-2.0
<!-- SPDX-License-Identifier: Apache-2.0 Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements; and to You under the Apache License, Version 2.0. --> ## What I Do - Provide detailed guidance for building Grails web applications and REST APIs. - Assist with GORM for data modeling, controllers for request handling, services for business logic, and views (GSP, JSON, Markup). - Support testing with Spock (unit/integration tests) and Geb for browser automation. - Guide plugin usage and development, security implementation, and deployment strategies. - Help with configuration, internationalization, async programming, and performance optimization. ## When to Use Me Activate this skill when developing with Grails, including: - Building CRUD applications, RESTful APIs, or full-stack web applications. - Working with GORM domain classes, constraints, and relationships. - Creating controllers, services, interceptors, or tag libraries. - Writing Spock specifications or Geb functional tests. - Configuring plugins, security, caching, or database migrations. - Deploying Grails applications to containers or cloud platforms. ## Technology Stack Grails is built on: - **Spring Boot**: 4.1.x - **Spring Framework**: 7.0.x - **Groovy**: 5.1.x - **Gradle**: 9.7 or later - **Spock**: 2.4-groovy-5.0 - **Jakarta EE**: 10 (migrated from javax.*) - **Micronaut**: Optional via `grails-micronaut` plugin ## Project Structure ``` myapp/ ├── grails-app/ │ ├── conf/ # Configuration │ │ ├── application.yml # Main config │ │ ├── logback-spring.xml # Logging config │ │ └── spring/resources.groovy # Spring bean definitions │ ├── controllers/ # Request handlers │ ├── domain/ # GORM domain classes │ ├── i18n/ # Message bundles │ ├── init/ # Bootstrap classes │ ├── services/ # Business logic │ ├── taglib/ # Custom GSP tags │ ├── utils/ # Utility classes │ └── views/ # GSP templates ├── src/ │ ├── main/groovy/ # Additional Groovy classes │ ├── main/java/ # Java classes │ ├── test/groovy/ # Test specifications │ └── integration-test/groovy/ # Integration test specifications ├── build.gradle # Build configuration └── gradle.properties # Project properties ``` ## Domain Classes (GORM) ### Basic Domain Class ```groovy class Book { String title String author Date datePublished Integer pages static constraints = { title blank: false, maxSize: 255 author blank: false datePublished nullable: true pages min: 1, nullable: true } static mapping = { table 'books' title column: 'book_title' datePublished type: 'date' } } ``` ### Relationships ```groovy class Author { String name static hasMany = [books: Book] static mapping = { books cascade: 'all-delete-orphan' } } class Book { String title static belongsTo = [author: Author] } // Many-to-Many class Book { static hasMany = [categories: Category] static belongsTo = Category } class Category { String name static hasMany = [books: Book] } ``` ### GORM Queries ```groovy // Dynamic finders Book.findByTitle("Grails Guide") Book.findAllByAuthorLike("%Smith%") Book.findByTitleAndAuthor("Guide", "Smith") Book.countByPagesGreaterThan(100) // Where queries Book.where { title =~ "%Grails%" && pages > 100 }.list() // Criteria queries Book.withCriteria { like('title', '%Grails%') gt('pages', 100) order('datePublished', 'desc') maxResults(10) } // HQL queries Book.executeQuery( "from Book b where b.author.name = :name", [name: 'Smith'] ) // Detached criteria (reusable) def criteria = Book.where { pages > 100 } criteria.list() criteria.count() ``` ### Transactions ```groovy // In services (default transactional) @Transactional class BookService { def saveBook(Book book) { book.save(failOnError: true) } } // Manual transaction control Book.withTransaction { status -> def book = new Book(title: "Test") book.save() if (someCondition) { status.setRollbackOnly() } } ``` ## Controllers ### Basic Controller ```groovy class BookController { BookService bookService static allowedMethods = [save: 'POST', update: 'PUT', delete: 'DELETE'] def index() { respond Book.list(), model: [bookCount: Book.count()] } def show(Long id) { respond Book.get(id) } def create() { respond new Book(params) } def save(Book book) { if (book.hasErrors()) { respond book.errors, view: 'create' return } bookService.save(book) redirect action: 'show', id: book.id } def edit(Long id) { respond Book.get(id) } def update(Book book) { if (book.hasErrors()) { respond book.errors, view: 'edit' return } bookService.save(book) redirect action: 'show', id: book.id } def delete(Long id) { bookService.delete(id) redirect action: 'index' } } ``` ### REST Controller ```groovy import grails.rest.RestfulController class BookController extends RestfulController<Book> { static responseFormats = ['json', 'xml'] BookController() { super(Book) } // Override to customize @Override protected Book queryForResource(Serializable id) { Book.where { id == id && active == true }.find() } } ``` ### Command Objects ```groovy class BookCommand implements Validateable { String title String author Integer pages static constraints = { title blank: false author blank: false pages min: 1, nullable: true } } class BookController { def save(BookCommand cmd) { if (cmd.hasErrors()) { respond cmd.errors return } // Process valid command } } ``` ### Content Negotiation ```groovy class BookController { def show(Long id) { def book = Book.get(id) respond book // Auto-negotiates based on Accept header } } // Or explicit def show(Long id) { def book = Book.get(id) withFormat { html { render view: 'show', model: [book: book] } json { render book as JSON } xml { render book as XML } } } ``` ## Services ### Basic Service ```groovy import grails.gorm.transactions.Transactional @Transactional class BookService { def findByTitle(String title) { Book.findByTitle(title) } def save(Book book) { book.save(failOnError: true) } @Transactional(readOnly = true) def list(Map params) { Book.list(params) } def delete(Long id) { Book.get(id)?.delete() } } ``` ### Service with Events ```groovy import grails.events.annotation.Publisher import grails.events.annotation.Subscriber class BookService { @Publisher def save(Book book) { book.save() // Automatically publishes 'book.saved' event } } class NotificationService { @Subscriber('book.saved') def onBookSaved(Book book) { // Handle event log.info "Book saved: ${book.title}" } } ``` ### Async Service ```groovy import grails.async.Promise import static grails.async.Promises.* class BookService { Promise<Book> findAsync(Long id) { task { Book.get(id) } } def processBooks() { def p1 = task { Book.findAllByActive(true) } def p2 = task { Author.list() } waitAll(p1, p2) // Both complete } } ``` ## Views ### GSP (Groovy Server Pages) ```html <!-- grails-app/views/book/list.gsp --> <!DOCTYPE html> <html> <head> <title>Books</title> <asset:stylesheet src="application.css"/> </head> <body> <h1>Books (${bookCount})</h1> <g:each in="${bookList}" var="book"> <div class="book"> <h2>${book.title}</h2> <p>By ${book.author}</p> <g:link action="show" id="${book.id}">View</g:link> </div> </g:each> <g:if test="${bookList.empty}"> <p>No books found.</p> </g:if> <g:paginate total="${bookCount}"/> <asset:javascript src="application.js"/> </body> </html> ``` ### Common GSP Tags ```html <!-- Links --> <g:link controller="book" action="show" id="${book.id}">View</g:link> <g:createLink action="list"/> <!-- Forms --> <g:form action="save"> <g:textField name="title" value="${book?.title}"/> <g:textArea name="description" value="${book?.description}"/> <g:select name="category" from="${categories}" optionKey="id" optionValue="name"/> <g:checkBox name="active" value="${book?.active}"/> <g:submitButton name="submit" value="Save"/> </g:form> <!-- Conditionals --> <g:if test="${condition}">...</g:if> <g:elseif test="${other}">...</g:elseif> <g:else>...</g:else> <!-- Iteration --> <g:each in="${items}" var="item" status="i"> ${i}: ${item.name} </g:each> <!-- Rendering --> <g:render template="bookRow" model="[book: book]"/> <g:render template="bookRow" collection="${books}" var="book"/> <!-- Security (with Spring Security plugin) --> <sec:ifLoggedIn>Welcome, ${sec.username()}</sec:ifLoggedIn> <sec:ifNotLoggedIn><g:link controller="login">Login</g:link></sec:ifNotLoggedIn> ``` ### JSON Views ```groovy // grails-app/views/book/show.gson import myapp.Book model { Book book } json { id book.id title book.title author book.author datePublished book.datePublished?.format('yyyy-MM-dd') _links { self { href "/books/${book.id}" } } } ``` ### Layouts ```html <!-- grails-app/views/layouts/main.gsp --> <!DOCTYPE html> <html> <head> <title><g:layoutTitle default="My App"/></title> <asset:stylesheet src="application.css"/> <g:layoutHead/> </head> <body> <nav><!-- Navigation --></nav> <main> <g:layoutBody/> </main> <footer><!-- Footer --></footer> <asset:javascript src="application.js"/> </body> </html> <!-- Use in views --> <html> <head> <meta name="layout" content="main"/> <title>Books</title> </head> <body> <!-- Page content --> </body> </html> ``` ## Interceptors ```groovy class AuthInterceptor { AuthInterceptor() { matchAll() .excludes(controller: 'login')
在 GitHub 查看
这个 SKILL.md 很大,SkillsMP 这里只预览前一段内容。 在 GitHub 查看