| @@ -0,0 +1,166 @@ | |||
| ### frontend: | |||
| - Routes.js (path: 'xxx**Win**' for Window component) | |||
| - load js resources: | |||
| ```javascript | |||
| // option 1 | |||
| items: [ | |||
| Ext.create('App.test.TestPanel',{ | |||
| title: 'Title 1' | |||
| }), | |||
| { | |||
| xtype: 'panel', | |||
| title: 'Title 2' | |||
| } | |||
| ] | |||
| // option 2 | |||
| requires: ['App.xxxx.XxxxPanel'], | |||
| items: [{ | |||
| xtype: 'test.testPanel', | |||
| title: 'Title 1' | |||
| }] | |||
| ``` | |||
| - get component e.g. | |||
| ```javascript | |||
| me.down('test\\.testPanel') //alias: 'widget.test.testPanel' | |||
| - i18n: static/public/i18n/{locale}.json | |||
| ```javascript | |||
| // component level | |||
| bind: { | |||
| text: '{i18n.xx.xx}' | |||
| } | |||
| // program level | |||
| App.getI18n('xx.xx') // or App.getI18n().xx.xx | |||
| - security | |||
| - App.security.getUser() | |||
| - App.security.isGranted(string | string[]) | |||
| - App.security.isGrantedAll(string[]) | |||
| ### backend (draft): | |||
| - vs code dev: example.launch.json | |||
| - db migration: resources/db/changelog/changes/\*\* (support subfolder) | |||
| - suggested that one changeset with one DDL | |||
| - filename: {yyyyMMdd}\_{number}\_{name}/{number}_{name}.sql | |||
| - filename 2(optional): {yyyyMMdd}\_{number}\_{name}/{number}\_{name}/{number}\_{name}.sql | |||
| ```sql | |||
| --liquibase formatted sql | |||
| --changeset {name}:{id} | |||
| --comment: remarks (optional) | |||
| CREATE TABLE `tableName` ( | |||
| `id` INT PRIMARY KEY AUTO_INCREMENT, | |||
| `created` DATETIME NOT NULL DEFAULT NOW(), | |||
| `createdBy` VARCHAR(30), | |||
| `version` INT NOT NULL DEFAULT 0, | |||
| `modified` DATETIME NOT NULL DEFAULT NOW(), | |||
| `modifiedBy` VARCHAR(30), | |||
| `deleted` BOOLEAN NOT NULL DEFAULT FALSE, | |||
| `column1` INT NOT NULL, | |||
| `column2` VARCHAR(255) | |||
| ); | |||
| ``` | |||
| - controller | |||
| - AbstractController | |||
| - api: /public/\*\* /protected/\*\* | |||
| - RequestBody Class (`@NotNull`, `@NotBlank`, `@Min`, `@Max`, `@Pattern`, etc...) | |||
| ```java | |||
| @NotNull | |||
| @Pattern(regexp = "^Y$|^N$") | |||
| private String YesNo; | |||
| ``` | |||
| - authentication check: `@PreAuthorize` | |||
| - common response class `DataRes`, `IdRes`, `RecrodsRes`, `SuccessRes` | |||
| - RESTfull api (http method, http status) | |||
| - http status: | |||
| - 404: record not found | |||
| - 409: record_line not found or record_line not under the record (relation check) | |||
| - 422: something wrong, (e.g. qty not enough) | |||
| - suggest /save api (also allowed for POST: new, PUT: update) | |||
| 1. POST /xxxx/save | |||
| 2. body: | |||
| ```json | |||
| { | |||
| "id": 1, //update | |||
| "xx": "xx", | |||
| "updateLines": [ | |||
| { | |||
| "id": null, // new | |||
| "xx": "xx" | |||
| }, | |||
| { | |||
| // need a relation check | |||
| "id": 1, //update | |||
| "xx": "xx" | |||
| } | |||
| ], | |||
| // need a relation check | |||
| "deleteLineIds": [1, 2] | |||
| } | |||
| ``` | |||
| ```java | |||
| //example | |||
| @RestController | |||
| @RequestMapping("/xxxx") | |||
| public class ExampleController extends AbstractController { | |||
| @PostMapping('/xxx') // @GetMapping @DeleteMapping @PutMapping @PatchMapping | |||
| //@ResponseStatus(HttpStatus.CREATED) | |||
| //@PreAuthorize("hasAuthority('XXX_MAINT')") | |||
| public Xxxx api (@RequestBody @Valid ReqClass req) { | |||
| } | |||
| @GetMapping('/{id}') | |||
| public Xxxx getXxx (@PathVariable int id) { | |||
| } | |||
| } | |||
| ``` | |||
| - service | |||
| - AbstractService, AbstractIdEntityService, AbstractBaseEntityService | |||
| - `@Transactional(rollbackFor = Exception.class)` for write, read only not need transaction | |||
| - dao | |||
| - interface | |||
| - AbstractDao | |||
| - [Query Creation](https://docs.spring.io/spring-data/jpa/docs/2.7.0/reference/html/#jpa.query-methods.query-creation) | |||
| - `@Query` | |||
| ```java | |||
| public Optional<User> findByUsernameAndDeletedFalse(String username); | |||
| // same | |||
| @Query("FROM #{#entityName} u WHERE u.deleted = FALSE AND u.username = :username") | |||
| public Optional<User> findByUsername(@Param("username") String username); | |||
| ``` | |||
| - entity | |||
| - IdEntity, BaseEntity | |||
| - validation: `@NotNull`, `@NotBlank`, `@Min`, `@Max`, `@Pattern`, etc... | |||
| - jdbcDao | |||
| - reduce Map: queryForEntity, queryForList("SELECT xxx" , params, XxxRecord.class) | |||
| - [Class Object vs Hashmap](https://stackoverflow.com/questions/10258071/class-object-vs-hashmap) | |||
| - reduce `@Autowired` | |||
| - use constructor | |||
| - try separate class (e.g. FileListController, FileDownloadController) | |||
| - return Optional instead of null | |||
| - return Optional\<T\> : may null | |||
| - return T : must not null | |||
| - i18n: i18n/messages_{locale(underscore)}.properties | |||
| - `messageSource.getMessage("name", null, LocaleUtils.getLocale());` | |||
| - reporting: | |||
| - excel: | |||
| - files: | |||
| - excel/{filename}.xlsx | |||
| - excel/{filename}_zh-TW.xlsx | |||
| - excel/{filename}_zh-CN.xlsx | |||
| - load Workbook: `ExcelUtils.loadTemplate("excel/{filename}");` | |||
| - send out Workbook: `ExcelUtils.send(response, workbook, "filename"); // will output filename.xlsx` | |||
| - jasper | |||
| - files: | |||
| - reports/{filename}.jrxml | |||
| - reports/{filename}_zh-TW.jrxml | |||
| - reports/{filename}_zh-CN.jrxml | |||
| - compile: `JasperUtils.compile("reports/{filename}", params, new JRBeanCollectionDataSource(xxx), Map.of("subReport1","reports/{filename}"));` | |||
| - send out: `JasperUtils.responsePdf(response, jRerpot, "filename"); // will output filename.pdf` | |||
| - mobile: | |||
| - login: POST /mobile/login | |||
| - logout: POST /mobile/logout | |||
| - call api: http headers with | |||
| 1. `X-ATT-DeviceId` | |||
| 2. `X-ATT-Access-Token` | |||