|
|
@@ -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` |