服务端代码
开发流程
遵循传统三层结构
实体定义 -> Repository定义 -> Service定义 -> Controller定义
实体定义
在 kernel 模块 cn.gson.financial.kernel.model.entity 包下创建业务实体类
实体类定义遵循jpa标准,字段定义和配置好后,启动项目可自动建表
⚠️注意事项:
1.账套关联数据,实体类需要继承 AbsEntity 类,AbsEntity定义了联合主键(accountSetsId,id)
2.非账套关联数据不需要继承
已凭证字模块为例:
@Getter
@Setter
@Entity
@Table(uniqueConstraints = {
@UniqueConstraint(name = "uc_voucherword_accountsetsid", columnNames = {"accountSetsId", "word"})
})
@DynamicUpdate
@DynamicInsert
@Comment("凭证字")
public class VoucherWord extends AbsEntity {
@Comment("凭证字")
@Column(nullable = false, length = 32)
private String word;
@Comment("打印标题")
@Column(nullable = false, length = 32)
private String printTitle;
@Comment("是否默认")
@ColumnDefault("b'1'")
@Column(nullable = false)
private Boolean isDefault;
}
@Comment 和 @ColumnDefault 是v4版本才支持,v3版本不支持这两个注解,v3可以通过 @Column 注解的 columnDefinition 属性实现类似功能
Repository定义
单表数据的 save 操作,需要通过JPA的Repository进行
在 kernel 模块 cn.gson.financial.kernel.model.repo 包下创建Repository
public interface VoucherWordRepository extends JpaRepository<VoucherWord, PrimaryKey> {
}
Service定义
在 kernel 模块 cn.gson.financial.kernel.service 包下创建Repository
service需要继承AbsService
public abstract class AbsService {
@Resource
protected SqlToyLazyDao lazyDao;
@Resource
protected JPAQueryFactory jqf;
@Resource
@Lazy
protected BlazeJPAQueryFactory bqf;
/**
* 雪花ID生成器
*/
@Resource
@Lazy
protected SnowflakeId snowflakeId;
}
AbsService提供了三个对象:
- lazyDao:sqlToy调用
- jqf:queryDsl的update和delete操作对象
- bqf:queryDsl的select操作对象
- snowflakeId:雪花ID生成器
lazyDao、jqf、bqf三个对象的使用可参考以下文档
- querydsl
- blazebit
- sqltoy
目前系统数据操作层面,大多数情况下querydsl能满足业务数据的查询操作,只有涉及到特别复杂的查询,以及需要传递大量参数的情况下,会需要用到sqltoy对象进行原生sql查询。例如报表模块的查询基本都用到了sqltoy的lazyDao进行操作
⚠️如果业务模块是账套关联数据,则服务需要实现 AccountSetsProcessor, BackupProcessor两个接口。如果未实现接口,则创建,删除和备份还原账套时会丢失该业务产生的数据。
AccountSetsProcessor 提供了账套初始化和重置时对相关业务数据的切入。
public interface AccountSetsProcessor {
/**
* 账套数据初始化
*
* @param accountSets
* @param accountSetsTemplate
*/
default void init(AccountSets accountSets, AccountSetsTemplate accountSetsTemplate) {
}
/**
* 账套数据清理
*
* @param accountSetsId
*/
void clear(Long accountSetsId);
}
BackupProcessor 提供了账套备份和还原时对相关业务数据的切入。
public interface BackupProcessor {
/**
* 备份
*
* @param accountSetsId
* @return
*/
String backup(Long accountSetsId);
/**
* 备份类型
*
* @return
*/
Class<?> backupType();
/**
* 恢复备份
*
* @param accountSetsId
* @return
*/
void recover(Long accountSetsId, String json);
}
已凭证字模块的服务类为例:
@Service
@RequiredArgsConstructor
public class VoucherWordService extends AbsService implements AccountSetsProcessor, BackupProcessor {
private final VoucherWordRepository voucherWordRepository;
private final QVoucherWord qVoucherWord = QVoucherWord.voucherWord;
@Transactional
@CacheRemove(value = "voucherWord", key = "#entity.accountSetsId")
public VoucherWord save(VoucherWord entity) {
if (entity.getPrimaryKey() == null) {
//判断是否重复
if (voucherWordRepository.count(qVoucherWord.word.eq(entity.getWord().trim())
.and(qVoucherWord.accountSetsId.eq(entity.getAccountSetsId()))) > 0) {
throw new ServiceException("亲,保存失败啦!凭证字【%s】已经存在!", entity.getWord());
}
} else {
//判断是否重复
if (voucherWordRepository.count(qVoucherWord.word.eq(entity.getWord().trim())
.and(qVoucherWord.accountSetsId.eq(entity.getAccountSetsId()))
.and(qVoucherWord.id.ne(entity.getId()))) > 0) {
throw new ServiceException("亲,保存失败啦!凭证字【%s】已经存在!", entity.getWord());
}
}
return voucherWordRepository.save(entity);
}
@Transactional
@CacheRemove(value = "voucherWord", key = "#accountSetsId")
public void remove(Long voucherWordId, Long accountSetsId) {
VoucherWord word = jqf.selectFrom(qVoucherWord).where(qVoucherWord.accountSetsId.eq(accountSetsId).and(qVoucherWord.id.eq(voucherWordId))).fetchFirst();
if (word.getIsDefault()) {
throw new ServiceException("默认凭证字不能被删除!");
}
voucherWordRepository.delete(word);
}
/**
* 如果新增为默认,则把其他的都设置为非默认
*
* @param entity
*/
@Transactional
@CacheRemove(value = "voucherWord", key = "#entity.accountSetsId")
public void updateDefault(VoucherWord entity) {
if (entity.getIsDefault()) {
jqf.update(qVoucherWord)
.set(qVoucherWord.isDefault, false)
.where(qVoucherWord.accountSetsId.eq(entity.getAccountSetsId()))
.execute();
jqf.update(qVoucherWord)
.set(qVoucherWord.isDefault, true)
.where(qVoucherWord.id.eq(entity.getId())
.and(qVoucherWord.accountSetsId.eq(entity.getAccountSetsId())))
.execute();
}
}
@Cacheable(value = "voucherWord", key = "#accountSetsId")
public List<VoucherWord> list(Long accountSetsId) {
return bqf.selectFrom(qVoucherWord)
.where(qVoucherWord.accountSetsId.eq(accountSetsId))
.orderBy(qVoucherWord.isDefault.desc())
.fetch();
}
@Transactional
@Override
@CacheRemove(value = "voucherWord", key = "#accountSets.id")
public void init(AccountSets accountSets, AccountSetsTemplate accountSetsTemplate) {
//默认凭证字
List<VoucherWord> defaultVw = bqf.selectFrom(qVoucherWord)
.where(qVoucherWord.accountSetsId.eq(accountSetsTemplate.getId()))
.fetch();
voucherWordRepository.saveAll(defaultVw.stream().map(vw -> {
VoucherWord bean = BeanUtil.toBean(vw, VoucherWord.class);
bean.setAccountSetsId(accountSets.getId());
return bean;
}).collect(Collectors.toList()));
}
@Transactional
@Override
@CacheRemove(value = "voucherWord", key = "#accountSetsId")
public void clear(Long accountSetsId) {
jqf.delete(QVoucherWord.voucherWord)
.where(QVoucherWord.voucherWord.accountSetsId.eq(accountSetsId))
.execute();
}
@Override
public String backup(Long accountSetsId) {
return JSON.toJSONString(bqf.selectFrom(qVoucherWord)
.where(qVoucherWord.accountSetsId.eq(accountSetsId)).fetch());
}
@Override
public Class<?> backupType() {
return VoucherWord.class;
}
@Override
@CacheRemove(value = "voucherWord", key = "#accountSetsId")
public void recover(Long accountSetsId, String json) {
List<VoucherWord> entityList = JSON.parseArray(json, VoucherWord.class);
for (VoucherWord entity : entityList) {
entity.setAccountSetsId(accountSetsId);
voucherWordRepository.save(entity);
}
}
}
⚠️ Service类中涉及到的Q类型类,例如QVoucherWord,是queryDsl框架根据系统中定义的实体类自动生成,不需要手动编写。
如果提示找不到此类,可重新build对应模块。
在模块build/generated/sources/annotationProcessor/java可看到动态生成的此类。
Controller定义
Controller定义在对应服务的server模块中
在 bs-server 模块 cn.gson.financial.controller 包下创建Controller
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/voucher-word")
public class VoucherWordController {
private final VoucherWordService voucherWordService;
@GetMapping
public JsonResult list(@SessionAccountSetId Long accountSetsId) {
return JsonResult.successful(voucherWordService.list(accountSetsId));
}
@PostMapping
@SaCheckPermission("setting-vouchergroup-canedit")
public JsonResult create(@RequestBody VoucherWord voucherWord, @SessionAccountSetId Long accountSetsId) {
voucherWord.setAccountSetsId(accountSetsId);
voucherWordService.save(voucherWord);
return JsonResult.successful();
}
@PutMapping
@SaCheckPermission("setting-vouchergroup-canedit")
public JsonResult update(@RequestBody VoucherWord voucherWord, @SessionAccountSetId Long accountSetsId) {
voucherWord.setAccountSetsId(accountSetsId);
voucherWordService.save(voucherWord);
return JsonResult.successful();
}
@DeleteMapping("{voucherWordId:\\d+}")
@SaCheckPermission("setting-vouchergroup-candelete")
public JsonResult delete(@PathVariable Long voucherWordId, @SessionAccountSetId Long accountSetsId) {
voucherWordService.remove(voucherWordId, accountSetsId);
return JsonResult.successful();
}
}
权限检查通过sa-token的 @SaCheckPermission 注解控制,权限key可再管理后台权限管理进行定义和查看。
Controller中可以通过
- @SessionAccountSetId:注解自动获取到当前登录用户的当前账套ID,
- @SessionAccountSet:获取当前账套对象,
- @SessionUser:获取当前登录用户对象。
具体实现 SessionUserHandlerMethodArgumentResolver.java:
@Component
public class SessionUserHandlerMethodArgumentResolver implements SessionHandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return ((parameter.getParameterType().isAssignableFrom(UserVo.class)) && parameter.hasParameterAnnotation(SessionUser.class))
|| ((parameter.getParameterType().isAssignableFrom(Long.class)) && parameter.hasParameterAnnotation(SessionAccountSetId.class))
|| ((parameter.getParameterType().isAssignableFrom(AccountSets.class)) && parameter.hasParameterAnnotation(SessionAccountSet.class));
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
UserVo user = StpUtil.getSession().getModel(S_USER_KEY, UserVo.class);
if (user != null) {
if (parameter.hasParameterAnnotation(SessionUser.class)) {
return user;
} else if (parameter.hasParameterAnnotation(SessionAccountSetId.class)) {
return user.getAccountSets() != null ? user.getAccountSets().getId() : null;
} else if (parameter.hasParameterAnnotation(SessionAccountSet.class)) {
return user.getAccountSets();
}
}
return null;
}
}