Commit 8e19e6f9 by yuwei

项目初始化

parent 04cdf60d
package cn.datax.auth.listener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class AuthenticationFailureEventListener implements ApplicationListener<AbstractAuthenticationFailureEvent> {
@Override
public void onApplicationEvent(AbstractAuthenticationFailureEvent event) {
log.info("{}登录失败", event.getAuthentication().getPrincipal());
}
}
package cn.datax.auth.listener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class AuthenticationSuccessEventListener implements ApplicationListener<AuthenticationSuccessEvent> {
@Override
public void onApplicationEvent(AuthenticationSuccessEvent event) {
if(!event.getSource().getClass().getName().equals("org.springframework.security.authentication.UsernamePasswordAuthenticationToken")){
log.info("{}登录成功", event.getAuthentication().getPrincipal());
}
}
}
...@@ -34,7 +34,6 @@ public class DataUserDetailService implements UserDetailsService { ...@@ -34,7 +34,6 @@ public class DataUserDetailService implements UserDetailsService {
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//远程获取用户 //远程获取用户
R result = userServiceFeign.loginByUsername(s); R result = userServiceFeign.loginByUsername(s);
log.info(JSON.toJSONString(result));
if(result == null || !result.isSuccess() || ObjectUtil.isEmpty(result.getData())){ if(result == null || !result.isSuccess() || ObjectUtil.isEmpty(result.getData())){
throw new UsernameNotFoundException(StrUtil.format("{}用户不存在", s)); throw new UsernameNotFoundException(StrUtil.format("{}用户不存在", s));
} }
......
...@@ -9,4 +9,8 @@ public class DataOauthException extends OAuth2Exception { ...@@ -9,4 +9,8 @@ public class DataOauthException extends OAuth2Exception {
public DataOauthException(String msg) { public DataOauthException(String msg) {
super(msg); super(msg);
} }
public DataOauthException(String msg, Throwable t) {
super(msg, t);
}
} }
...@@ -17,14 +17,10 @@ import org.springframework.web.HttpRequestMethodNotSupportedException; ...@@ -17,14 +17,10 @@ import org.springframework.web.HttpRequestMethodNotSupportedException;
@Slf4j @Slf4j
@Component @Component
public class DataWebResponseExceptionTranslator implements WebResponseExceptionTranslator { public class DataWebResponseExceptionTranslator implements WebResponseExceptionTranslator<OAuth2Exception> {
private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer(); private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();
public void setThrowableAnalyzer(ThrowableAnalyzer throwableAnalyzer) {
this.throwableAnalyzer = throwableAnalyzer;
}
@Override @Override
public ResponseEntity<OAuth2Exception> translate(Exception e) { public ResponseEntity<OAuth2Exception> translate(Exception e) {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
...@@ -86,12 +82,16 @@ public class DataWebResponseExceptionTranslator implements WebResponseExceptionT ...@@ -86,12 +82,16 @@ public class DataWebResponseExceptionTranslator implements WebResponseExceptionT
}else if (StringUtils.containsIgnoreCase(e.getMessage(), "Access token expired")) { }else if (StringUtils.containsIgnoreCase(e.getMessage(), "Access token expired")) {
message = "Token过期"; message = "Token过期";
}else if (StringUtils.containsIgnoreCase(e.getMessage(), "locked")) { }else if (StringUtils.containsIgnoreCase(e.getMessage(), "locked")) {
message = "用户已被锁定,请联系管理员"; message = "用户已被锁定";
}else if (StringUtils.containsIgnoreCase(e.getMessage(), "Bad credentials")) { }else if (StringUtils.containsIgnoreCase(e.getMessage(), "Bad credentials")) {
message = "用户名或密码错误"; message = "用户名或密码错误";
} }
} }
return new ResponseEntity<>(new DataOauthException(message), headers, HttpStatus.valueOf(status)); return new ResponseEntity<OAuth2Exception>(new DataOauthException(message, e), headers, HttpStatus.valueOf(status));
}
public void setThrowableAnalyzer(ThrowableAnalyzer throwableAnalyzer) {
this.throwableAnalyzer = throwableAnalyzer;
} }
@SuppressWarnings("serial") @SuppressWarnings("serial")
......
package cn.datax.common.redis.config; package cn.datax.common.redis.config;
import cn.datax.common.redis.service.RedisService;
import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.cache.interceptor.KeyGenerator;
...@@ -47,6 +50,7 @@ public class RedisConfig extends CachingConfigurerSupport { ...@@ -47,6 +50,7 @@ public class RedisConfig extends CachingConfigurerSupport {
} }
@Bean @Bean
@ConditionalOnBean(name = "redisTemplate")
public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) { public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {
//spring cache注解序列化配置 //spring cache注解序列化配置
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig() RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
...@@ -82,6 +86,7 @@ public class RedisConfig extends CachingConfigurerSupport { ...@@ -82,6 +86,7 @@ public class RedisConfig extends CachingConfigurerSupport {
* @return * @return
*/ */
@Bean @Bean
@ConditionalOnClass(RedisOperations.class)
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
...@@ -99,4 +104,10 @@ public class RedisConfig extends CachingConfigurerSupport { ...@@ -99,4 +104,10 @@ public class RedisConfig extends CachingConfigurerSupport {
redisTemplate.afterPropertiesSet(); redisTemplate.afterPropertiesSet();
return redisTemplate; return redisTemplate;
} }
@Bean
@ConditionalOnBean(name = "redisTemplate")
public RedisService redisService() {
return new RedisService();
}
} }
\ No newline at end of file
package cn.datax.service.system.controller; package cn.datax.service.system.controller;
import cn.datax.common.base.BaseController; import cn.datax.common.base.BaseController;
import cn.datax.common.core.DataUser;
import cn.datax.common.core.R; import cn.datax.common.core.R;
import cn.datax.common.security.annotation.DataInner; import cn.datax.common.security.annotation.DataInner;
import cn.datax.common.utils.SecurityUtil;
import cn.datax.service.system.api.vo.UserInfo; import cn.datax.service.system.api.vo.UserInfo;
import cn.datax.service.system.service.UserService; import cn.datax.service.system.service.UserService;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken; import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.util.Base64Utils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
@Slf4j @Slf4j
@RestController @RestController
...@@ -35,28 +24,6 @@ public class LoginController extends BaseController { ...@@ -35,28 +24,6 @@ public class LoginController extends BaseController {
@Autowired @Autowired
private TokenStore tokenStore; private TokenStore tokenStore;
@Autowired
private RestTemplate restTemplate;
@GetMapping("/token")
public R getTokenUser() {
DataUser user = SecurityUtil.getDataUser();
return R.ok().setData(user);
}
@DataInner
@PostMapping("/login")
public R login(){
MultiValueMap<String, String> formData = new LinkedMultiValueMap();
HttpHeaders headers = new HttpHeaders();
// headers.set("Authorization", this.getAuthorizationHeader(this.clientId, this.clientSecret));
Map<String, Object> map = this.postForMap("", formData, headers);
if (map.containsKey("error")) {
return R.error();
}
return R.ok().setData(map);
}
@DataInner @DataInner
@GetMapping("/login/username/{username}") @GetMapping("/login/username/{username}")
public R loginByUsername(@PathVariable String username) { public R loginByUsername(@PathVariable String username) {
...@@ -78,14 +45,4 @@ public class LoginController extends BaseController { ...@@ -78,14 +45,4 @@ public class LoginController extends BaseController {
tokenStore.removeRefreshToken(refreshToken); tokenStore.removeRefreshToken(refreshToken);
return R.ok(); return R.ok();
} }
private String getAuthorizationHeader(String clientId, String clientSecret) {
String creds = String.format("%s:%s", clientId, clientSecret);
return "Basic " + new String(Base64Utils.encode(creds.getBytes()));
}
private Map<String, Object> postForMap(String path, MultiValueMap<String, String> formData, HttpHeaders headers) {
Map map = restTemplate.exchange(path, HttpMethod.POST, new HttpEntity(formData, headers), Map.class, new Object[0]).getBody();
return map;
}
} }
...@@ -18,4 +18,8 @@ dateformat=yyyy-MM-dd HH:mm:ss ...@@ -18,4 +18,8 @@ dateformat=yyyy-MM-dd HH:mm:ss
# 是否开启慢SQL记录 # 是否开启慢SQL记录
outagedetection=true outagedetection=true
# 慢SQL记录标准 2 秒 # 慢SQL记录标准 2 秒
outagedetectioninterval=2 outagedetectioninterval=2
\ No newline at end of file # 开启过滤
filter=true
# 配置不打印的内容
exclude=select 1
\ No newline at end of file
...@@ -57,6 +57,7 @@ public class CodeGenerator { ...@@ -57,6 +57,7 @@ public class CodeGenerator {
pc.setParent("cn.datax.service"); pc.setParent("cn.datax.service");
// 父包模块名 // 父包模块名
pc.setModuleName("system"); pc.setModuleName("system");
pc.setMapper("dao");
mpg.setPackageInfo(pc); mpg.setPackageInfo(pc);
// 自定义配置 // 自定义配置
...@@ -67,13 +68,10 @@ public class CodeGenerator { ...@@ -67,13 +68,10 @@ public class CodeGenerator {
} }
}; };
// 如果模板引擎是 velocity
String templatePath = "/templates/mapper.xml.vm";
// 自定义输出配置 // 自定义输出配置
List<FileOutConfig> focList = new ArrayList<>(); List<FileOutConfig> focList = new ArrayList<>();
// 自定义配置会被优先输出 // 自定义配置会被优先输出
focList.add(new FileOutConfig(templatePath) { focList.add(new FileOutConfig("/templates/mapper.xml.vm") {
@Override @Override
public String outputFile(TableInfo tableInfo) { public String outputFile(TableInfo tableInfo) {
// 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!! // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
...@@ -124,9 +122,10 @@ public class CodeGenerator { ...@@ -124,9 +122,10 @@ public class CodeGenerator {
strategy.setControllerMappingHyphenStyle(true); strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix("sys_"); strategy.setTablePrefix("sys_");
// strategy.setInclude(new String[]{"sys_user", "sys_role", "sys_dept", "sys_menu", "sys_post", "sys_user_role", "sys_user_dept", "sys_user_post", "sys_role_menu"}); // strategy.setInclude(new String[]{"sys_user", "sys_role", "sys_dept", "sys_menu", "sys_post", "sys_user_role", "sys_user_dept", "sys_user_post", "sys_role_menu"});
strategy.setInclude(new String[]{"sys_dept_relation", "sys_role_dept"}); strategy.setInclude(new String[]{"sys_role_dept"});
mpg.setStrategy(strategy); mpg.setStrategy(strategy);
mpg.setTemplateEngine(new VelocityTemplateEngine()); mpg.setTemplateEngine(new VelocityTemplateEngine());
mpg.execute(); mpg.execute();
} }
......
package ${package.Controller};
import org.springframework.web.bind.annotation.RequestMapping;
#if(${restControllerStyle})
import org.springframework.web.bind.annotation.RestController;
#else
import org.springframework.stereotype.Controller;
#end
#if(${superControllerClassPackage})
import ${superControllerClassPackage};
#end
/**
* <p>
* $!{table.comment} 前端控制器
* </p>
*
* @author ${author}
* @since ${date}
*/
#if(${restControllerStyle})
@RestController
#else
@Controller
#end
@RequestMapping("#if(${package.ModuleName})/${package.ModuleName}#end/#if(${controllerMappingHyphenStyle})${controllerMappingHyphen}#else${table.entityPath}#end")
#if(${kotlin})
class ${table.controllerName}#if(${superControllerClass}) : ${superControllerClass}()#end
#else
#if(${superControllerClass})
public class ${table.controllerName} extends ${superControllerClass} {
#else
public class ${table.controllerName} {
#end
}
#end
\ No newline at end of file
package ${package.Entity};
#foreach($pkg in ${table.importPackages})
import ${pkg};
#end
#if(${swagger2})
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
#end
#if(${entityLombokModel})
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
#end
/**
* <p>
* $!{table.comment}
* </p>
*
* @author ${author}
* @since ${date}
*/
#if(${entityLombokModel})
@Data
#if(${superEntityClass})
@EqualsAndHashCode(callSuper = true)
#else
@EqualsAndHashCode(callSuper = false)
#end
@Accessors(chain = true)
#end
#if(${table.convert})
@TableName("${table.name}")
#end
#if(${swagger2})
@ApiModel(value="${entity}对象", description="$!{table.comment}")
#end
#if(${superEntityClass})
public class ${entity} extends ${superEntityClass}#if(${activeRecord})<${entity}>#end {
#elseif(${activeRecord})
public class ${entity} extends Model<${entity}> {
#else
public class ${entity} implements Serializable {
#end
#if(${entitySerialVersionUID})
private static final long serialVersionUID=1L;
#end
## ---------- BEGIN 字段循环遍历 ----------
#foreach($field in ${table.fields})
#if(${field.keyFlag})
#set($keyPropertyName=${field.propertyName})
#end
#if("$!field.comment" != "")
#if(${swagger2})
@ApiModelProperty(value = "${field.comment}")
#else
/**
* ${field.comment}
*/
#end
#end
#if(${field.keyFlag})
## 主键
#if(${field.keyIdentityFlag})
@TableId(value = "${field.name}", type = IdType.AUTO)
#elseif(!$null.isNull(${idType}) && "$!idType" != "")
@TableId(value = "${field.name}", type = IdType.${idType})
#elseif(${field.convert})
@TableId("${field.name}")
#end
## 普通字段
#elseif(${field.fill})
## ----- 存在字段填充设置 -----
#if(${field.convert})
@TableField(value = "${field.name}", fill = FieldFill.${field.fill})
#else
@TableField(fill = FieldFill.${field.fill})
#end
#elseif(${field.convert})
@TableField("${field.name}")
#end
## 乐观锁注解
#if(${versionFieldName}==${field.name})
@Version
#end
## 逻辑删除注解
#if(${logicDeleteFieldName}==${field.name})
@TableLogic
#end
private ${field.propertyType} ${field.propertyName};
#end
## ---------- END 字段循环遍历 ----------
#if(!${entityLombokModel})
#foreach($field in ${table.fields})
#if(${field.propertyType.equals("boolean")})
#set($getprefix="is")
#else
#set($getprefix="get")
#end
public ${field.propertyType} ${getprefix}${field.capitalName}() {
return ${field.propertyName};
}
#if(${entityBuilderModel})
public ${entity} set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
#else
public void set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
#end
this.${field.propertyName} = ${field.propertyName};
#if(${entityBuilderModel})
return this;
#end
}
#end
#end
#if(${entityColumnConstant})
#foreach($field in ${table.fields})
public static final String ${field.name.toUpperCase()} = "${field.name}";
#end
#end
#if(${activeRecord})
@Override
protected Serializable pkVal() {
#if(${keyPropertyName})
return this.${keyPropertyName};
#else
return null;
#end
}
#end
#if(!${entityLombokModel})
@Override
public String toString() {
return "${entity}{" +
#foreach($field in ${table.fields})
#if($!{foreach.index}==0)
"${field.propertyName}=" + ${field.propertyName} +
#else
", ${field.propertyName}=" + ${field.propertyName} +
#end
#end
"}";
}
#end
}
package ${package.Entity};
#foreach($pkg in ${table.importPackages})
import ${pkg};
#end
#if(${swagger2})
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
#end
/**
* <p>
* $!{table.comment}
* </p>
*
* @author ${author}
* @since ${date}
*/
#if(${table.convert})
@TableName("${table.name}")
#end
#if(${swagger2})
@ApiModel(value="${entity}对象", description="$!{table.comment}")
#end
#if(${superEntityClass})
class ${entity} : ${superEntityClass}#if(${activeRecord})<${entity}>#end() {
#elseif(${activeRecord})
class ${entity} : Model<${entity}>() {
#else
class ${entity} : Serializable {
#end
## ---------- BEGIN 字段循环遍历 ----------
#foreach($field in ${table.fields})
#if(${field.keyFlag})
#set($keyPropertyName=${field.propertyName})
#end
#if("$!field.comment" != "")
#if(${swagger2})
@ApiModelProperty(value = "${field.comment}")
#else
/**
* ${field.comment}
*/
#end
#end
#if(${field.keyFlag})
## 主键
#if(${field.keyIdentityFlag})
@TableId(value = "${field.name}", type = IdType.AUTO)
#elseif(!$null.isNull(${idType}) && "$!idType" != "")
@TableId(value = "${field.name}", type = IdType.${idType})
#elseif(${field.convert})
@TableId("${field.name}")
#end
## 普通字段
#elseif(${field.fill})
## ----- 存在字段填充设置 -----
#if(${field.convert})
@TableField(value = "${field.name}", fill = FieldFill.${field.fill})
#else
@TableField(fill = FieldFill.${field.fill})
#end
#elseif(${field.convert})
@TableField("${field.name}")
#end
## 乐观锁注解
#if(${versionFieldName}==${field.name})
@Version
#end
## 逻辑删除注解
#if(${logicDeleteFieldName}==${field.name})
@TableLogic
#end
#if(${field.propertyType} == "Integer")
var ${field.propertyName}: Int? = null
#else
var ${field.propertyName}: ${field.propertyType}? = null
#end
#end
## ---------- END 字段循环遍历 ----------
#if(${entityColumnConstant})
companion object {
#foreach($field in ${table.fields})
const val ${field.name.toUpperCase()} : String = "${field.name}"
#end
}
#end
#if(${activeRecord})
override fun pkVal(): Serializable? {
#if(${keyPropertyName})
return ${keyPropertyName}
#else
return null
#end
}
#end
override fun toString(): String {
return "${entity}{" +
#foreach($field in ${table.fields})
#if($!{foreach.index}==0)
"${field.propertyName}=" + ${field.propertyName} +
#else
", ${field.propertyName}=" + ${field.propertyName} +
#end
#end
"}"
}
}
package ${package.Mapper};
import ${package.Entity}.${entity};
import ${superMapperClassPackage};
/**
* <p>
* $!{table.comment} Mapper 接口
* </p>
*
* @author ${author}
* @since ${date}
*/
#if(${kotlin})
interface ${table.mapperName} : ${superMapperClass}<${entity}>
#else
public interface ${table.mapperName} extends ${superMapperClass}<${entity}> {
}
#end
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="${package.Mapper}.${table.mapperName}">
#if(${enableCache})
<!-- 开启二级缓存 -->
<cache type="org.mybatis.caches.ehcache.LoggingEhcache"/>
#end
#if(${baseResultMap})
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="${package.Entity}.${entity}">
#foreach($field in ${table.fields})
#if(${field.keyFlag})##生成主键排在第一位
<id column="${field.name}" property="${field.propertyName}" />
#end
#end
#foreach($field in ${table.commonFields})##生成公共字段
<result column="${field.name}" property="${field.propertyName}" />
#end
#foreach($field in ${table.fields})
#if(!${field.keyFlag})##生成普通字段
<result column="${field.name}" property="${field.propertyName}" />
#end
#end
</resultMap>
#end
#if(${baseColumnList})
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
#foreach($field in ${table.commonFields})
${field.name},
#end
${table.fieldNames}
</sql>
#end
</mapper>
package ${package.Service};
import ${package.Entity}.${entity};
import ${superServiceClassPackage};
/**
* <p>
* $!{table.comment} 服务类
* </p>
*
* @author ${author}
* @since ${date}
*/
#if(${kotlin})
interface ${table.serviceName} : ${superServiceClass}<${entity}>
#else
public interface ${table.serviceName} extends ${superServiceClass}<${entity}> {
}
#end
package ${package.ServiceImpl};
import ${package.Entity}.${entity};
import ${package.Mapper}.${table.mapperName};
import ${package.Service}.${table.serviceName};
import ${superServiceImplClassPackage};
import org.springframework.stereotype.Service;
/**
* <p>
* $!{table.comment} 服务实现类
* </p>
*
* @author ${author}
* @since ${date}
*/
@Service
#if(${kotlin})
open class ${table.serviceImplName} : ${superServiceImplClass}<${table.mapperName}, ${entity}>(), ${table.serviceName} {
}
#else
public class ${table.serviceImplName} extends ${superServiceImplClass}<${table.mapperName}, ${entity}> implements ${table.serviceName} {
}
#end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment