# FAQ
# Target Audience
This document is intended for developers and implementers of this system.
# How to Manually Inject a Service Definition in Customization Code
During project implementation, some common logic may be defined in the system as a service for code reuse. In customization Groovy code, you can easily look up the service definition and call its methods.
Use code like the following to look up a service from the bean registry by name or by type and call it. When looking up by name, the name is the Service class name with a lowercase first letter; for example, AuthorityService corresponds to authorityService.
import tech.muyan.BeanHelper
import tech.muyan.security.AuthorityService
// Look up by name
AuthorityService authorityService = BeanHelper.getBean("authorityService")
// Or look up by type
AuthorityService sameService = BeanHelper.getBean(AuthorityService)
2
3
4
5
6
7
8
# A Default Filter Is Defined for the Form but Does Not Take Effect
The current frontend does not read dynamic filters (DynamicFilter). List pages do not show any filters, and filters marked as default (isDefault) are not applied automatically. See Dynamic Filter for details.
To give a list default filter conditions, set listForm.searchConditions in the form's extInfo; see Default Filter Conditions.
# The extInfo Field Cannot Be Imported from CSV
When the content of the extInfo field (stored as JSON in the database) contains double quotes ("), make sure they are escaped in the imported CSV with two double quotes (""), not with a backslash (\).
# Sharing Global Configuration, Connections, or Data Caching Between Dynamic Logic Executions
In some scenarios you may need to share data across multiple Dynamic Logic executions. Typical scenarios include:
- Storing and sharing cached data that is expensive to compute
- Configuration or resources that must be shared globally, such as stateful connections to third-party systems, or managing Kafka, Message Queue and similar connections
- Different Dynamic Logic executions that unavoidably have an order in the business logic, where the results of earlier steps can be cached.
The system provides the tech.muyan.helper.RegistryHelper class for managing globally shared data. You can use
RegistryHelper.memoryGet(key)to get cached data,RegistryHelper.memoryPut(key, value)to set cached data; it returns the value previously stored under the key (or null if there was none),RegistryHelper.memoryRemove(key)to remove cached data.
# The Backend Starts Normally, but the Frontend Cannot Log In or the API Returns 401
The platform's configuration is in the environment variables of docker-compose.yml. Check the following:
- Whether the value of
X-Muyan-Tenantinruntime/proxy/conf.d/default.confmatches the backend'sTENANT_ID. When they do not match, the backend returnsTenant xxx not found in system; see Changing the Default Tenant. - After changing or adding
JWT_SECRET, all previously issued tokens become invalid and you need to log in again; if the browser still carries an old token, clear the browser cache as described in the next item.JWT_SECRETmust be at least 32 bytes; if it is too short, the backend fails to start.
# After Going Live, Frontend Requests to the Backend Fail, Users Cannot Log In, or the Page Is Blank
Try the following:
- On the web server, delete or force-refresh the nginx cache
- In the browser, clear the entries in the frontend's
localStorage - In the browser, delete or force-refresh the browser cache
# Saving an Object Fails When a Nullable Primitive Field in the Domain Is Empty
In the Domain definition, declare nullable primitive fields with the corresponding wrapper type, for example change int to Integer, double to Double, boolean to Boolean, and so on.
# A Field in the Domain Is Not Shown in the Frontend
Check whether the field is defined in the constraints of the Domain definition:
static constraints = {
attachments nullable: true
}
2
3
Here is an example configuration:
class Feedback implements Serializable, MultiTenant<Feedback>,
Auditable, HasComment<Feedback> {
// Other fields omitted
List<StorageFieldValue> attachments
static hasMany = [attachments: StorageFieldValue]
static constraints = {
// Other constraints omitted
attachments nullable: true
}
}
2
3
4
5
6
7
8
9
10
11
12
# Associations Cannot Be Saved in a One-to-Many Relationship
For a working definition, see DynamicMenu.groovy, which defines a parent field pointing to itself and a children field as the back reference.
For the GORM documentation, see GORM Associations (opens new window).
class DynamicMenu implements MultiTenant<DynamicMenu>,
Auditable, Serializable, RenderableObject {
// Other fields omitted
// Field on the Many side pointing to the One side
DynamicMenu parent
// Field on the One side pointing to the Many side
static hasMany = [children: DynamicMenu]
// Note: do not define the children field explicitly as List<DynamicMenu> children,
// otherwise GORM cannot save the parent field
static constraints = {
// Other constraints omitted
parent nullable: true
children nullable: true
}
static mapping = {
// No mapping is needed for the children field; it is only used as a back reference
parent index: 'dynamic_menu_parent_idx'
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# How to Define a New WebSocket Endpoint
The current platform version provides only one fixed WebSocket entry point; the frontend connects to it through nginx at /api/websocket/route, and new WebSocket paths can no longer be registered. To handle a new message type, extend tech.muyan.api.websocket.MuyanWebSocketComponent in a plugin to receive and push messages by topic; see Plugin Development ยท WebSocket.