diff --git a/common/src/main/java/com/storeroom/config/RedisConfig.java b/common/src/main/java/com/storeroom/config/RedisConfig.java new file mode 100644 index 0000000..09a574e --- /dev/null +++ b/common/src/main/java/com/storeroom/config/RedisConfig.java @@ -0,0 +1,198 @@ +package com.storeroom.config; + + +import cn.hutool.core.lang.Assert; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.parser.ParserConfig; +import com.alibaba.fastjson.serializer.SerializerFeature; +import com.storeroom.utils.StringUtils; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.digest.DigestUtils; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.data.redis.RedisProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cache.Cache; +import org.springframework.cache.annotation.CachingConfigurerSupport; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.interceptor.CacheErrorHandler; +import org.springframework.cache.interceptor.KeyGenerator; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisOperations; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.RedisSerializationContext; +import org.springframework.data.redis.serializer.RedisSerializer; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; + +@Slf4j +@Configuration +@EnableCaching +@ConditionalOnClass(RedisOperations.class) +@EnableConfigurationProperties(RedisProperties.class) +public class RedisConfig extends CachingConfigurerSupport { + + /** + * 设置 redis 数据默认过期时间,默认2小时 + * 设置@cacheable 序列化方式 + */ + @Bean + public RedisCacheConfiguration redisCacheConfiguration(){ + FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class); + RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig(); + configuration = configuration.serializeValuesWith(RedisSerializationContext. + SerializationPair.fromSerializer(fastJsonRedisSerializer)).entryTtl(Duration.ofHours(6)); + return configuration; + } + + @SuppressWarnings("all") + @Bean(name = "redisTemplate") + @ConditionalOnMissingBean(name = "redisTemplate") + public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { + RedisTemplate template = new RedisTemplate<>(); + //序列化 + FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class); + // value值的序列化采用fastJsonRedisSerializer + template.setValueSerializer(fastJsonRedisSerializer); + template.setHashValueSerializer(fastJsonRedisSerializer); + // 全局开启AutoType,这里方便开发,使用全局的方式 + ParserConfig.getGlobalInstance().setAutoTypeSupport(true); + // 建议使用这种方式,小范围指定白名单 + // ParserConfig.getGlobalInstance().addAccept("me.zhengjie.domain"); + // key的序列化采用StringRedisSerializer + template.setKeySerializer(new StringRedisSerializer()); + template.setHashKeySerializer(new StringRedisSerializer()); + template.setConnectionFactory(redisConnectionFactory); + return template; + } + + /** + * 自定义缓存key生成策略,默认将使用该策略 + */ + @Bean + @Override + public KeyGenerator keyGenerator() { + return (target, method, params) -> { + Map container = new HashMap<>(3); + Class targetClassClass = target.getClass(); + // 类地址 + container.put("class",targetClassClass.toGenericString()); + // 方法名称 + container.put("methodName",method.getName()); + // 包名称 + container.put("package",targetClassClass.getPackage()); + // 参数列表 + for (int i = 0; i < params.length; i++) { + container.put(String.valueOf(i),params[i]); + } + // 转为JSON字符串 + String jsonString = JSON.toJSONString(container); + // 做SHA256 Hash计算,得到一个SHA256摘要作为Key + return DigestUtils.sha256Hex(jsonString); + }; + } + + @Bean + @Override + public CacheErrorHandler errorHandler() { + // 异常处理,当Redis发生异常时,打印日志,但是程序正常走 + log.info("初始化 -> [{}]", "Redis CacheErrorHandler"); + return new CacheErrorHandler() { + @Override + public void handleCacheGetError(RuntimeException e, Cache cache, Object key) { + log.error("Redis occur handleCacheGetError:key -> [{}]", key, e); + } + + @Override + public void handleCachePutError(RuntimeException e, Cache cache, Object key, Object value) { + log.error("Redis occur handleCachePutError:key -> [{}];value -> [{}]", key, value, e); + } + + @Override + public void handleCacheEvictError(RuntimeException e, Cache cache, Object key) { + log.error("Redis occur handleCacheEvictError:key -> [{}]", key, e); + } + + @Override + public void handleCacheClearError(RuntimeException e, Cache cache) { + log.error("Redis occur handleCacheClearError:", e); + } + }; + } + +} + +/** + * Value 序列化 + * + * @author / + * @param + */ +class FastJsonRedisSerializer implements RedisSerializer { + + private final Class clazz; + + FastJsonRedisSerializer(Class clazz) { + super(); + this.clazz = clazz; + } + + @Override + public byte[] serialize(T t) { + if (t == null) { + return new byte[0]; + } + return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(StandardCharsets.UTF_8); + } + + @Override + public T deserialize(byte[] bytes) { + if (bytes == null || bytes.length <= 0) { + return null; + } + String str = new String(bytes, StandardCharsets.UTF_8); + return JSON.parseObject(str, clazz); + } + +} + +/** + * 重写序列化器 + * + * @author / + */ +class StringRedisSerializer implements RedisSerializer { + + private final Charset charset; + + StringRedisSerializer() { + this(StandardCharsets.UTF_8); + } + + private StringRedisSerializer(Charset charset) { + Assert.notNull(charset, "Charset must not be null!"); + this.charset = charset; + } + + @Override + public String deserialize(byte[] bytes) { + return (bytes == null ? null : new String(bytes, charset)); + } + + @Override + public byte[] serialize(Object object) { + String string = JSON.toJSONString(object); + if (StringUtils.isBlank(string)) { + return null; + } + string = string.replace("\"", ""); + return string.getBytes(charset); + } +} diff --git a/common/src/main/java/com/storeroom/config/SwaggerConfig.java b/common/src/main/java/com/storeroom/config/SwaggerConfig.java new file mode 100644 index 0000000..1fee6e6 --- /dev/null +++ b/common/src/main/java/com/storeroom/config/SwaggerConfig.java @@ -0,0 +1,109 @@ +package com.storeroom.config; + +import com.fasterxml.classmate.TypeResolver; +import com.google.common.base.Predicates; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.data.domain.Pageable; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.ParameterBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.schema.AlternateTypeRule; +import springfox.documentation.schema.AlternateTypeRuleConvention; +import springfox.documentation.schema.ModelRef; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.service.Parameter; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +import java.util.ArrayList; +import java.util.List; + +import static com.google.common.collect.Lists.newArrayList; +import static springfox.documentation.schema.AlternateTypeRules.newRule; + + +@Configuration +@EnableSwagger2 +public class SwaggerConfig { + + @Value("${jwt.header}") + private String tokenHeader; + + @Value("${jwt.token-start-with}") + private String tokenStartWith; + + @Value("${swagger.enabled}") + private Boolean enabled; + + @Bean + @SuppressWarnings("all") + public Docket createRestApi() { + ParameterBuilder ticketPar = new ParameterBuilder(); + List pars = new ArrayList<>(); + ticketPar.name(tokenHeader).description("token") + .modelRef(new ModelRef("string")) + .parameterType("header") + .defaultValue(tokenStartWith + " ") + .required(true) + .build(); + pars.add(ticketPar.build()); + return new Docket(DocumentationType.SWAGGER_2) + .enable(enabled) + .apiInfo(apiInfo()) + .select() + .paths(Predicates.not(PathSelectors.regex("/error.*"))) + .build() + .globalOperationParameters(pars); + } + + private ApiInfo apiInfo() { + return new ApiInfoBuilder() + .description("阅行客后台管理系统") + .title("YXK-ADMIN 接口文档") + .version("2.4") + .build(); + } + +} + +/** + * 将Pageable转换展示在swagger中 + */ +@Configuration +class SwaggerDataConfig { + + @Bean + public AlternateTypeRuleConvention pageableConvention(final TypeResolver resolver) { + return new AlternateTypeRuleConvention() { + @Override + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE; + } + + @Override + public List rules() { + return newArrayList(newRule(resolver.resolve(Pageable.class), resolver.resolve(Page.class))); + } + }; + } + + @ApiModel + @Data + private static class Page { + @ApiModelProperty("页码 (0..N)") + private Integer page; + + @ApiModelProperty("每页显示的数目") + private Integer size; + + @ApiModelProperty("以下列格式排序标准:property[,asc | desc]。 默认排序顺序为升序。 支持多种排序条件:如:id,asc") + private List sort; + } +} diff --git a/common/src/main/java/com/storeroom/utils/RsaUtils.java b/common/src/main/java/com/storeroom/utils/RsaUtils.java index 28a14d8..cf3a010 100644 --- a/common/src/main/java/com/storeroom/utils/RsaUtils.java +++ b/common/src/main/java/com/storeroom/utils/RsaUtils.java @@ -85,6 +85,8 @@ public class RsaUtils { System.out.println("***************** 私钥加密公钥解密结束 *****************"); } + + /** * 公钥解密 * diff --git a/system/src/main/java/com/storeroom/modules/security/controller/AuthorizationController.java b/system/src/main/java/com/storeroom/modules/security/controller/AuthorizationController.java index ea1248b..64f8d57 100644 --- a/system/src/main/java/com/storeroom/modules/security/controller/AuthorizationController.java +++ b/system/src/main/java/com/storeroom/modules/security/controller/AuthorizationController.java @@ -16,13 +16,11 @@ import com.storeroom.modules.security.service.OnlineUserService; import com.storeroom.modules.security.service.dto.AuthUserDto; import com.storeroom.modules.security.service.dto.JwtUserDto; import com.storeroom.utils.*; -import com.storeroom.utils.enums.DataStatusEnum; import com.wf.captcha.base.Captcha; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; @@ -113,6 +111,7 @@ public class AuthorizationController { put("img", captcha.toBase64()); put("uuid", uuid); }}; + //return ApiResponse.success(imgResult); return ApiResponse.success(imgResult); } diff --git a/system/src/main/java/com/storeroom/modules/security/controller/OnlineController.java b/system/src/main/java/com/storeroom/modules/security/controller/OnlineController.java new file mode 100644 index 0000000..f43ac6d --- /dev/null +++ b/system/src/main/java/com/storeroom/modules/security/controller/OnlineController.java @@ -0,0 +1,52 @@ +package com.storeroom.modules.security.controller; + + +import com.storeroom.modules.security.service.OnlineUserService; +import com.storeroom.utils.EncryptUtils; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Set; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/auth/online") +@Api(tags = "系统:在线用户管理") +public class OnlineController { + + private final OnlineUserService onlineUserService; + + @ApiOperation("查询在线用户") + @GetMapping + //@PreAuthorize("@el.check()") + public ResponseEntity query(String filter, Pageable pageable){ + return new ResponseEntity<>(onlineUserService.getAll(filter, pageable), HttpStatus.OK); + } + + @ApiOperation("导出数据") + @GetMapping(value = "/download") + //@PreAuthorize("@el.check()") + public void download(HttpServletResponse response, String filter) throws IOException { + onlineUserService.download(onlineUserService.getAll(filter), response); + } + + @ApiOperation("踢出用户") + @DeleteMapping + //@PreAuthorize("@el.check()") + public ResponseEntity delete(@RequestBody Set keys) throws Exception { + for (String key : keys) { + // 解密Key + key = EncryptUtils.desDecrypt(key); + onlineUserService.kickOut(key); + } + return new ResponseEntity<>(HttpStatus.OK); + } +} diff --git a/system/src/main/java/com/storeroom/modules/system/domain/User.java b/system/src/main/java/com/storeroom/modules/system/domain/User.java index 7038b47..6ef5121 100644 --- a/system/src/main/java/com/storeroom/modules/system/domain/User.java +++ b/system/src/main/java/com/storeroom/modules/system/domain/User.java @@ -84,23 +84,21 @@ public class User extends BaseEntity implements Serializable { @ApiModelProperty(value = "是否为admin账号", hidden = true) private Boolean isAdmin = false; + + @Column(name = "pwd_reset_time") @ApiModelProperty(value = "最后修改密码的时间", hidden = true) private Date pwdResetTime; @Override public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; User user = (User) o; - return Objects.equals(id, user.id) && - Objects.equals(username, user.username); + return Objects.equals(id, user.id) && Objects.equals(roles, user.roles) && Objects.equals(jobs, user.jobs) && Objects.equals(dept, user.dept) && Objects.equals(username, user.username) && Objects.equals(nickName, user.nickName) && Objects.equals(email, user.email) && Objects.equals(phone, user.phone) && Objects.equals(gender, user.gender) && Objects.equals(avatarName, user.avatarName) && Objects.equals(avatarPath, user.avatarPath) && Objects.equals(password, user.password) && Objects.equals(enabled, user.enabled) && Objects.equals(isAdmin, user.isAdmin) && Objects.equals(pwdResetTime, user.pwdResetTime); } + @Override public int hashCode() { return Objects.hash(id, username); diff --git a/system/src/main/resources/application-prod.yml b/system/src/main/resources/application-prod.yml index e69de29..e7835cd 100644 --- a/system/src/main/resources/application-prod.yml +++ b/system/src/main/resources/application-prod.yml @@ -0,0 +1,117 @@ +#配置数据源 +spring: + datasource: + druid: + db-type: com.alibaba.druid.pool.DruidDataSource + driverClassName: net.sf.log4jdbc.sql.jdbcapi.DriverSpy + url: jdbc:log4jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:yxkadmin}?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false + username: ${DB_USER:root} + password: ${DB_PWD:ftzn83560792} + # 初始连接数 + initial-size: 5 + # 最小连接数 + min-idle: 10 + # 最大连接数 + max-active: 20 + # 获取连接超时时间 + max-wait: 5000 + # 连接有效性检测时间 + time-between-eviction-runs-millis: 60000 + # 连接在池中最小生存的时间 + min-evictable-idle-time-millis: 300000 + # 连接在池中最大生存的时间 + max-evictable-idle-time-millis: 900000 + test-while-idle: true + test-on-borrow: false + test-on-return: false + # 检测连接是否有效 + validation-query: select 1 + # 配置监控统计 + webStatFilter: + enabled: true + stat-view-servlet: + enabled: true + url-pattern: /druid/* + reset-enable: false + login-username: admin + login-password: 123456 + filter: + stat: + enabled: true + # 记录慢SQL + log-slow-sql: true + slow-sql-millis: 1000 + merge-sql: true + wall: + config: + multi-statement-allow: true + +# 登录相关配置 +login: + # 登录缓存 + cache-enable: true + # 是否限制单用户登录 + single-login: false + # 验证码 + login-code: + # 验证码类型配置 查看 LoginProperties 类 + code-type: arithmetic + # 登录图形验证码有效时间/分钟 + expiration: 2 + # 验证码高度 + width: 111 + # 验证码宽度 + heigth: 36 + # 内容长度 + length: 2 + # 字体名称,为空则使用默认字体,如遇到线上乱码,设置其他字体即可 + font-name: + # 字体大小 + font-size: 25 + +#jwt +jwt: + header: Authorization + # 令牌前缀 + token-start-with: Bearer + # 必须使用最少88位的Base64对该令牌进行编码 + base64-secret: ZmQ0ZGI5NjQ0MDQwY2I4MjMxY2Y3ZmI3MjdhN2ZmMjNhODViOTg1ZGE0NTBjMGM4NDA5NzYxMjdjOWMwYWRmZTBlZjlhNGY3ZTg4Y2U3YTE1ODVkZDU5Y2Y3OGYwZWE1NzUzNWQ2YjFjZDc0NGMxZWU2MmQ3MjY1NzJmNTE0MzI= + # 令牌过期时间 此处单位/毫秒 ,默认2小时,可在此网站生成 https://www.convertworld.com/zh-hans/time/milliseconds.html + token-validity-in-seconds: 7200000 + # 在线用户key + online-key: online-token- + # 验证码 + code-key: code-key- + # token 续期检查时间范围(默认30分钟,单位默认毫秒),在token即将过期的一段时间内用户操作了,则给用户的token续期 + detect: 1800000 + # 续期时间范围,默认 1小时,这里单位毫秒 + renew: 3600000 + +# IP 本地解析 +ip: + local-parsing: false + +#是否允许生成代码,生产环境设置为false +generator: + enabled: false + + + +#是否开启 swagger-ui +swagger: + enabled: true + +# 文件存储路径 +file: + mac: + path: ~/file/ + avatar: ~/avatar/ + linux: + path: /home/www/yxkadmin/file/ + avatar: /home/www/yxkadmin/avatar/ + windows: + path: D:\yxk-storetoom\file\ + avatar: D:\yxk-storetoom\avatar\ + # 文件大小 /M + maxSize: 100 + avatarMaxSize: 5 \ No newline at end of file diff --git a/system/src/main/resources/application.yml b/system/src/main/resources/application.yml index 9a32865..c8dc866 100644 --- a/system/src/main/resources/application.yml +++ b/system/src/main/resources/application.yml @@ -60,8 +60,7 @@ ip: #密码加密传输 此处为私钥 rsa: - private_key: MIICWwIBAAKBgQDyhGeZ23kNfDysndtEMHQgX+3YcKPNny2YFtlfvYTpHmVClbaiZK+g2VetX/mhkiSQtyp5OwSy+dm6WVX7QOvUO7iPNs2Hl+33d6rzzMhROs46rCTBpukgdaT0N6SC8NWkBF9ogoDzhEGNKIV0AGHRPWmL75ghflUPD2VflmAnywIDAQABAoGAPL47MMdPD7ihfd7gD7lPLNi6Oy8jaBpJkkGO2rMeekFZvY7AOvabIt+tXUifvv9a10B5i/njWGzKQymjJpaBOp+JswuBKEq8ZFqeyQJWsrj1zJC+HaCImP1frvgdtWIu1tldVE44X7jIQSFAd26JJsPslMntj/iE7VBkzQRYhjkCQQD/3+zu4OJP5XdSq4Y+HzIJ7u2JS8u/SxRQYQh1E6/yydgyWE96cze4jZQQTvxBRsOrXvBlVJdT+QmcV0YnlyMtAkEA8qLOBevOgH5qq1nLRMYU6zb0k5OiAaiI+Oq1mZn0kjcisyFZopAs2FH07DVlh+tzbGdzt4Q3PjLSEqhVFU4x1wJARljoKRzG27R4w8/IjpfBCB4aTF78W1Fm+lpTGu0YuKVpvR2ubDn1HdY+2OT+UWwFK75kVVeWa03SqJsN/KB+2QJAKD9XO2Y1F91gZlH7xMmyuJ2iDkTD79B8AAY232bJSeO5bstOagfOWIenv/LPh69Hsyip6jwVScz2ScAAdQtGewJACeSyLWjPnQ4ZgWrJyz6dyPgGKprDzQ00UMlvH3F2KJ9Ll99yfBah++GQXPfzjhD2ouWAQ+GaiS/RdSNDX1RoFg== - + private_key: MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA0vfvyTdGJkdbHkB8mp0f3FE0GYP3AYPaJF7jUd1M0XxFSE2ceK3k2kw20YvQ09NJKk+OMjWQl9WitG9pB6tSCQIDAQABAkA2SimBrWC2/wvauBuYqjCFwLvYiRYqZKThUS3MZlebXJiLB+Ue/gUifAAKIg1avttUZsHBHrop4qfJCwAI0+YRAiEA+W3NK/RaXtnRqmoUUkb59zsZUBLpvZgQPfj1MhyHDz0CIQDYhsAhPJ3mgS64NbUZmGWuuNKp5coY2GIj/zYDMJp6vQIgUueLFXv/eZ1ekgz2Oi67MNCk5jeTF2BurZqNLR3MSmUCIFT3Q6uHMtsB9Eha4u7hS31tj1UWE+D+ADzp59MGnoftAiBeHT7gDMuqeJHPL4b+kC+gzV4FGTfhR9q3tTbklZkD2A== # 内存用户缓存配置 user-cache: diff --git a/system/src/main/resources/ip2region/ip2region.db b/system/src/main/resources/ip2region/ip2region.db new file mode 100644 index 0000000..43e1daf Binary files /dev/null and b/system/src/main/resources/ip2region/ip2region.db differ diff --git a/system/src/main/resources/template/email/email.ftl b/system/src/main/resources/template/email/email.ftl new file mode 100644 index 0000000..11cc14e --- /dev/null +++ b/system/src/main/resources/template/email/email.ftl @@ -0,0 +1,48 @@ + + + + + + + +
+
+

尊敬的用户,您好:

+

您正在申请邮箱验证,您的验证码为:

+

${code}

+
+
+
+ Copyright ©${.now?string("yyyy")} yxk-ADMIN 阅行客后台管理系统. +
+ +
+
+ + diff --git a/system/src/main/resources/template/email/taskAlarm.ftl b/system/src/main/resources/template/email/taskAlarm.ftl new file mode 100644 index 0000000..7b8c00f --- /dev/null +++ b/system/src/main/resources/template/email/taskAlarm.ftl @@ -0,0 +1,69 @@ + + + + + + + +
+
+

任务信息:

+ + + + + + + + + + + + + + + + + +
任务名称Bean名称执行方法参数内容Cron表达式描述内容
${task.jobName}${task.beanName}${task.methodName}${(task.params)!""}${task.cronExpression}${(task.description)!""}
+
+
+

异常信息:

+
+                ${msg}
+            
+
+
+
+
+ Copyright ©${.now?string("yyyy")} YXK-ADMIN 阅行客后台管理系统. +
+ +
+ + + diff --git a/system/src/main/resources/template/generator/admin/Controller.ftl b/system/src/main/resources/template/generator/admin/Controller.ftl new file mode 100644 index 0000000..6293a8e --- /dev/null +++ b/system/src/main/resources/template/generator/admin/Controller.ftl @@ -0,0 +1,73 @@ + +package ${package}.rest; + +import com.yxkadmin.annotation.Log; +import ${package}.domain.${className}; +import ${package}.service.${className}Service; +import ${package}.service.dto.${className}QueryCriteria; +import org.springframework.data.domain.Pageable; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import io.swagger.annotations.*; +import java.io.IOException; +import javax.servlet.http.HttpServletResponse; + +/** +* @website https://yxk-admin.vip +* @author ${author} +* @date ${date} +**/ +@RestController +@RequiredArgsConstructor +@Api(tags = "${apiAlias}管理") +@RequestMapping("/api/${changeClassName}") +public class ${className}Controller { + + private final ${className}Service ${changeClassName}Service; + + @Log("导出数据") + @ApiOperation("导出数据") + @GetMapping(value = "/download") + @PreAuthorize("@el.check('${changeClassName}:list')") + public void download(HttpServletResponse response, ${className}QueryCriteria criteria) throws IOException { + ${changeClassName}Service.download(${changeClassName}Service.queryAll(criteria), response); + } + + @GetMapping + @Log("查询${apiAlias}") + @ApiOperation("查询${apiAlias}") + @PreAuthorize("@el.check('${changeClassName}:list')") + public ResponseEntity query(${className}QueryCriteria criteria, Pageable pageable){ + return new ResponseEntity<>(${changeClassName}Service.queryAll(criteria,pageable),HttpStatus.OK); + } + + @PostMapping + @Log("新增${apiAlias}") + @ApiOperation("新增${apiAlias}") + @PreAuthorize("@el.check('${changeClassName}:add')") + public ResponseEntity create(@Validated @RequestBody ${className} resources){ + return new ResponseEntity<>(${changeClassName}Service.create(resources),HttpStatus.CREATED); + } + + @PutMapping + @Log("修改${apiAlias}") + @ApiOperation("修改${apiAlias}") + @PreAuthorize("@el.check('${changeClassName}:edit')") + public ResponseEntity update(@Validated @RequestBody ${className} resources){ + ${changeClassName}Service.update(resources); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + + @Log("删除${apiAlias}") + @ApiOperation("删除${apiAlias}") + @PreAuthorize("@el.check('${changeClassName}:del')") + @DeleteMapping + public ResponseEntity delete(@RequestBody ${pkColumnType}[] ids) { + ${changeClassName}Service.deleteAll(ids); + return new ResponseEntity<>(HttpStatus.OK); + } +} \ No newline at end of file diff --git a/system/src/main/resources/template/generator/admin/Dto.ftl b/system/src/main/resources/template/generator/admin/Dto.ftl new file mode 100644 index 0000000..402b4b9 --- /dev/null +++ b/system/src/main/resources/template/generator/admin/Dto.ftl @@ -0,0 +1,40 @@ + +package ${package}.service.dto; + +import lombok.Data; +<#if hasTimestamp> +import java.sql.Timestamp; + +<#if hasBigDecimal> +import java.math.BigDecimal; + +import java.io.Serializable; +<#if !auto && pkColumnType = 'Long'> +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; + + +/** +* @website https://yxk-admin.vip +* @description / +* @author ${author} +* @date ${date} +**/ +@Data +public class ${className}Dto implements Serializable { +<#if columns??> + <#list columns as column> + + <#if column.remark != ''> + /** ${column.remark} */ + + <#if column.columnKey = 'PRI'> + <#if !auto && pkColumnType = 'Long'> + /** 防止精度丢失 */ + @JsonSerialize(using= ToStringSerializer.class) + + + private ${column.columnType} ${column.changeColumnName}; + + +} \ No newline at end of file diff --git a/system/src/main/resources/template/generator/admin/Entity.ftl b/system/src/main/resources/template/generator/admin/Entity.ftl new file mode 100644 index 0000000..0386637 --- /dev/null +++ b/system/src/main/resources/template/generator/admin/Entity.ftl @@ -0,0 +1,71 @@ + +package ${package}.domain; + +import lombok.Data; +import cn.hutool.core.bean.BeanUtil; +import io.swagger.annotations.ApiModelProperty; +import cn.hutool.core.bean.copier.CopyOptions; +import javax.persistence.*; +<#if isNotNullColumns??> +import javax.validation.constraints.*; + +<#if hasDateAnnotation> +import javax.persistence.Entity; +import javax.persistence.Table; +import org.hibernate.annotations.*; + +<#if hasTimestamp> +import java.sql.Timestamp; + +<#if hasBigDecimal> +import java.math.BigDecimal; + +import java.io.Serializable; + +/** +* @website https://yxk-admin +* @description / +* @author ${author} +* @date ${date} +**/ +@Entity +@Data +@Table(name="${tableName}") +public class ${className} implements Serializable { +<#if columns??> + <#list columns as column> + + <#if column.columnKey = 'PRI'> + @Id + <#if auto> + @GeneratedValue(strategy = GenerationType.IDENTITY) + + + @Column(name = "${column.columnName}"<#if column.columnKey = 'UNI'>,unique = true<#if column.istNotNull && column.columnKey != 'PRI'>,nullable = false) + <#if column.istNotNull && column.columnKey != 'PRI'> + <#if column.columnType = 'String'> + @NotBlank + <#else> + @NotNull + + + <#if (column.dateAnnotation)??> + <#if column.dateAnnotation = 'CreationTimestamp'> + @CreationTimestamp + <#else> + @UpdateTimestamp + + + <#if column.remark != ''> + @ApiModelProperty(value = "${column.remark}") + <#else> + @ApiModelProperty(value = "${column.changeColumnName}") + + private ${column.columnType} ${column.changeColumnName}; + + + + public void copy(${className} source){ + BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true)); + } +} \ No newline at end of file diff --git a/system/src/main/resources/template/generator/admin/Mapper.ftl b/system/src/main/resources/template/generator/admin/Mapper.ftl new file mode 100644 index 0000000..490eea6 --- /dev/null +++ b/system/src/main/resources/template/generator/admin/Mapper.ftl @@ -0,0 +1,18 @@ + +package ${package}.service.mapstruct; + +import com.yxkadmin.base.BaseMapper; +import ${package}.domain.${className}; +import ${package}.service.dto.${className}Dto; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** +* @website https://yxk-admin.vip +* @author ${author} +* @date ${date} +**/ +@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface ${className}Mapper extends BaseMapper<${className}Dto, ${className}> { + +} \ No newline at end of file diff --git a/system/src/main/resources/template/generator/admin/QueryCriteria.ftl b/system/src/main/resources/template/generator/admin/QueryCriteria.ftl new file mode 100644 index 0000000..f4e5184 --- /dev/null +++ b/system/src/main/resources/template/generator/admin/QueryCriteria.ftl @@ -0,0 +1,67 @@ + +package ${package}.service.dto; + +import lombok.Data; +<#if queryHasTimestamp> +import java.sql.Timestamp; + +<#if queryHasBigDecimal> +import java.math.BigDecimal; + +<#if betweens??> +import java.util.List; + +<#if queryColumns??> +import me.zhengjie.annotation.Query; + + +/** +* @website https://el-admin.vip +* @author ${author} +* @date ${date} +**/ +@Data +public class ${className}QueryCriteria{ +<#if queryColumns??> + <#list queryColumns as column> + +<#if column.queryType = '='> + /** 精确 */ + @Query + private ${column.columnType} ${column.changeColumnName}; + +<#if column.queryType = 'Like'> + /** 模糊 */ + @Query(type = Query.Type.INNER_LIKE) + private ${column.columnType} ${column.changeColumnName}; + +<#if column.queryType = '!='> + /** 不等于 */ + @Query(type = Query.Type.NOT_EQUAL) + private ${column.columnType} ${column.changeColumnName}; + +<#if column.queryType = 'NotNull'> + /** 不为空 */ + @Query(type = Query.Type.NOT_NULL) + private ${column.columnType} ${column.changeColumnName}; + +<#if column.queryType = '>='> + /** 大于等于 */ + @Query(type = Query.Type.GREATER_THAN) + private ${column.columnType} ${column.changeColumnName}; + +<#if column.queryType = '<='> + /** 小于等于 */ + @Query(type = Query.Type.LESS_THAN) + private ${column.columnType} ${column.changeColumnName}; + + + +<#if betweens??> + <#list betweens as column> + /** BETWEEN */ + @Query(type = Query.Type.BETWEEN) + private List<${column.columnType}> ${column.changeColumnName}; + + +} \ No newline at end of file diff --git a/system/src/main/resources/template/generator/admin/Repository.ftl b/system/src/main/resources/template/generator/admin/Repository.ftl new file mode 100644 index 0000000..08d5129 --- /dev/null +++ b/system/src/main/resources/template/generator/admin/Repository.ftl @@ -0,0 +1,26 @@ + +package ${package}.repository; + +import ${package}.domain.${className}; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +/** +* @website https://yxk-admin.vip +* @author ${author} +* @date ${date} +**/ +public interface ${className}Repository extends JpaRepository<${className}, ${pkColumnType}>, JpaSpecificationExecutor<${className}> { +<#if columns??> + <#list columns as column> + <#if column.columnKey = 'UNI'> + /** + * 根据 ${column.capitalColumnName} 查询 + * @param ${column.columnName} / + * @return / + */ + ${className} findBy${column.capitalColumnName}(${column.columnType} ${column.columnName}); + + + +} \ No newline at end of file diff --git a/system/src/main/resources/template/generator/admin/Service.ftl b/system/src/main/resources/template/generator/admin/Service.ftl new file mode 100644 index 0000000..74c1bf5 --- /dev/null +++ b/system/src/main/resources/template/generator/admin/Service.ftl @@ -0,0 +1,69 @@ + +package ${package}.service; + +import ${package}.domain.${className}; +import ${package}.service.dto.${className}Dto; +import ${package}.service.dto.${className}QueryCriteria; +import org.springframework.data.domain.Pageable; +import java.util.Map; +import java.util.List; +import java.io.IOException; +import javax.servlet.http.HttpServletResponse; + +/** +* @website https://yxk-admin.vip +* @description 服务接口 +* @author ${author} +* @date ${date} +**/ +public interface ${className}Service { + + /** + * 查询数据分页 + * @param criteria 条件 + * @param pageable 分页参数 + * @return Map + */ + Map queryAll(${className}QueryCriteria criteria, Pageable pageable); + + /** + * 查询所有数据不分页 + * @param criteria 条件参数 + * @return List<${className}Dto> + */ + List<${className}Dto> queryAll(${className}QueryCriteria criteria); + + /** + * 根据ID查询 + * @param ${pkChangeColName} ID + * @return ${className}Dto + */ + ${className}Dto findById(${pkColumnType} ${pkChangeColName}); + + /** + * 创建 + * @param resources / + * @return ${className}Dto + */ + ${className}Dto create(${className} resources); + + /** + * 编辑 + * @param resources / + */ + void update(${className} resources); + + /** + * 多选删除 + * @param ids / + */ + void deleteAll(${pkColumnType}[] ids); + + /** + * 导出数据 + * @param all 待导出的数据 + * @param response / + * @throws IOException / + */ + void download(List<${className}Dto> all, HttpServletResponse response) throws IOException; +} \ No newline at end of file diff --git a/system/src/main/resources/template/generator/admin/ServiceImpl.ftl b/system/src/main/resources/template/generator/admin/ServiceImpl.ftl new file mode 100644 index 0000000..e4734e0 --- /dev/null +++ b/system/src/main/resources/template/generator/admin/ServiceImpl.ftl @@ -0,0 +1,143 @@ + +package ${package}.service.impl; + +import ${package}.domain.${className}; +<#if columns??> + <#list columns as column> + <#if column.columnKey = 'UNI'> + <#if column_index = 1> +import me.zhengjie.exception.EntityExistException; + + + + +import com.yxkadmin.utils.ValidationUtil; +import com.yxkadmin.utils.FileUtil; +import lombok.RequiredArgsConstructor; +import ${package}.repository.${className}Repository; +import ${package}.service.${className}Service; +import ${package}.service.dto.${className}Dto; +import ${package}.service.dto.${className}QueryCriteria; +import ${package}.service.mapstruct.${className}Mapper; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +<#if !auto && pkColumnType = 'Long'> +import cn.hutool.core.lang.Snowflake; +import cn.hutool.core.util.IdUtil; + +<#if !auto && pkColumnType = 'String'> +import cn.hutool.core.util.IdUtil; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import com.yxkadmin.utils.PageUtil; +import com.yxkadmin.utils.QueryHelp; +import java.util.List; +import java.util.Map; +import java.io.IOException; +import javax.servlet.http.HttpServletResponse; +import java.util.ArrayList; +import java.util.LinkedHashMap; + +/** +* @website https://yxk-admin.vip +* @description 服务实现 +* @author ${author} +* @date ${date} +**/ +@Service +@RequiredArgsConstructor +public class ${className}ServiceImpl implements ${className}Service { + + private final ${className}Repository ${changeClassName}Repository; + private final ${className}Mapper ${changeClassName}Mapper; + + @Override + public Map queryAll(${className}QueryCriteria criteria, Pageable pageable){ + Page<${className}> page = ${changeClassName}Repository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable); + return PageUtil.toPage(page.map(${changeClassName}Mapper::toDto)); + } + + @Override + public List<${className}Dto> queryAll(${className}QueryCriteria criteria){ + return ${changeClassName}Mapper.toDto(${changeClassName}Repository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder))); + } + + @Override + @Transactional + public ${className}Dto findById(${pkColumnType} ${pkChangeColName}) { + ${className} ${changeClassName} = ${changeClassName}Repository.findById(${pkChangeColName}).orElseGet(${className}::new); + ValidationUtil.isNull(${changeClassName}.get${pkCapitalColName}(),"${className}","${pkChangeColName}",${pkChangeColName}); + return ${changeClassName}Mapper.toDto(${changeClassName}); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public ${className}Dto create(${className} resources) { +<#if !auto && pkColumnType = 'Long'> + Snowflake snowflake = IdUtil.createSnowflake(1, 1); + resources.set${pkCapitalColName}(snowflake.nextId()); + +<#if !auto && pkColumnType = 'String'> + resources.set${pkCapitalColName}(IdUtil.simpleUUID()); + +<#if columns??> + <#list columns as column> + <#if column.columnKey = 'UNI'> + if(${changeClassName}Repository.findBy${column.capitalColumnName}(resources.get${column.capitalColumnName}()) != null){ + throw new EntityExistException(${className}.class,"${column.columnName}",resources.get${column.capitalColumnName}()); + } + + + + return ${changeClassName}Mapper.toDto(${changeClassName}Repository.save(resources)); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void update(${className} resources) { + ${className} ${changeClassName} = ${changeClassName}Repository.findById(resources.get${pkCapitalColName}()).orElseGet(${className}::new); + ValidationUtil.isNull( ${changeClassName}.get${pkCapitalColName}(),"${className}","id",resources.get${pkCapitalColName}()); +<#if columns??> + <#list columns as column> + <#if column.columnKey = 'UNI'> + <#if column_index = 1> + ${className} ${changeClassName}1 = null; + + ${changeClassName}1 = ${changeClassName}Repository.findBy${column.capitalColumnName}(resources.get${column.capitalColumnName}()); + if(${changeClassName}1 != null && !${changeClassName}1.get${pkCapitalColName}().equals(${changeClassName}.get${pkCapitalColName}())){ + throw new EntityExistException(${className}.class,"${column.columnName}",resources.get${column.capitalColumnName}()); + } + + + + ${changeClassName}.copy(resources); + ${changeClassName}Repository.save(${changeClassName}); + } + + @Override + public void deleteAll(${pkColumnType}[] ids) { + for (${pkColumnType} ${pkChangeColName} : ids) { + ${changeClassName}Repository.deleteById(${pkChangeColName}); + } + } + + @Override + public void download(List<${className}Dto> all, HttpServletResponse response) throws IOException { + List> list = new ArrayList<>(); + for (${className}Dto ${changeClassName} : all) { + Map map = new LinkedHashMap<>(); + <#list columns as column> + <#if column.columnKey != 'PRI'> + <#if column.remark != ''> + map.put("${column.remark}", ${changeClassName}.get${column.capitalColumnName}()); + <#else> + map.put(" ${column.changeColumnName}", ${changeClassName}.get${column.capitalColumnName}()); + + + + list.add(map); + } + FileUtil.downloadExcel(list, response); + } +} \ No newline at end of file diff --git a/system/src/main/resources/template/generator/front/api.ftl b/system/src/main/resources/template/generator/front/api.ftl new file mode 100644 index 0000000..9587d0d --- /dev/null +++ b/system/src/main/resources/template/generator/front/api.ftl @@ -0,0 +1,27 @@ +import request from '@/utils/request' + +export function add(data) { + return request({ + url: 'api/${changeClassName}', + method: 'post', + data + }) +} + +export function del(ids) { + return request({ + url: 'api/${changeClassName}/', + method: 'delete', + data: ids + }) +} + +export function edit(data) { + return request({ + url: 'api/${changeClassName}', + method: 'put', + data + }) +} + +export default { add, edit, del } diff --git a/system/src/main/resources/template/generator/front/index.ftl b/system/src/main/resources/template/generator/front/index.ftl new file mode 100644 index 0000000..9307821 --- /dev/null +++ b/system/src/main/resources/template/generator/front/index.ftl @@ -0,0 +1,175 @@ +<#--noinspection ALL--> + + + + +