diff --git a/config.py b/config.py new file mode 100644 index 0000000..21c6776 --- /dev/null +++ b/config.py @@ -0,0 +1,62 @@ +# 作者 +AUTHOR = "snail" +# 微信公众号 +WECHAT = "ot_bus" +# 子项目默认项目包名 +BASE_PACKAGE = "com.xr.api" +# 默认模块名称,控制controller的URL的前缀 +DEFAULT_PREFIX_URL = "models" +# 项目主目录 +# health-ai 主项目; +MAIN_MODULE = "health-ai" +#health-user-api 模块(子项目) +MODULE_NAME = "health-user-api" +# 项目根目录 +BASE_DIR = "./" +# 输出目录 +# 是否生成REST风格 +REST_CONTROLLER_STYLE = True +# 默认的GROUP_ID +GROUP_ID = "com.xr.health" +OUTPUT_DIR = f"./{MAIN_MODULE}/{MODULE_NAME}/src/main" + +DB = { + "host": "192.168.1.80", + "user": "root", + "password": "Khq#P9hZ4L@EwCZw", + "database": "health_ai_b", + "port": 3728 +} + +APPLICATION = { + "name": MODULE_NAME, + "version": "1.0.0", + "author": AUTHOR, + "package": {"base": BASE_PACKAGE}, + "redis": { + "host": "192.168.1.87", + "port": 6379, + "password": "redis_Hkhtz7", + "database": 4 + }, + "minio": { + "host": "192.168.1.88", + "port": "9001", + "accessKey": "BHB4X5zgZXuzQE6ku31e", + "secretKey": "NZgHX5LhtAVD1KdaucL0EVWy6IQk4owdZLA2A7wi", + "bucketName": "health-bucket", + "downloadDir": "/data/excel" + }, + "xxlJob":{ + "addresses": "http://192.168.1.88:8800/xxl-job-admin", + "accessToken": "xxljobTokenQ", + "address": "192.168.1.88:9999", + "ip": "192.168.1.88", + "port": "9998", + }, + "eureka":{ + "host": "92.168.1.87", + "port": "8761" + }, + "db": DB +} diff --git a/config.yml b/config.yml new file mode 100644 index 0000000..a4b3d9c --- /dev/null +++ b/config.yml @@ -0,0 +1,86 @@ +# =============================== +# 基础项目信息 +# =============================== +mainModule: test-ai +moduleName: test-user-api +groupId: com.test.ai +author: snail +wechat: ot_bus +baseDir: "./" +outputDir: /${mainModule}/${moduleName}/src/main +# 是否启用 Lombok +entityLombokModel: true + +# 是否生成 REST 风格 Controller +restControllerStyle: true + +# =============================== +# 包路径配置 +# =============================== +package: + Models: models + Base: com.test.api + Common: ${package.Base}.${package.Models}.common + Entity: ${package.Base}.${package.Models}.entity + Service: ${package.Base}.${package.Models}.service + ServiceImpl: ${package.Base}.${package.Models}.service.impl + Controller: ${package.Base}.${package.Models}.controller + Mapper: ${package.Base}.${package.Models}.mapper + +# =============================== +# 数据库配置 +# =============================== +db: + host: 192.168.1.80 + port: 3728 + user: root + password: Khq#P9hZ4L@EwCZw + database: health_ai_b + +# =============================== +# 应用级配置 +# =============================== +application: + name: ${moduleName} + version: 1.0.0 + author: snail + + package: + base: ${package.Base} + + # ---------- Redis ---------- + redis: + host: 192.168.1.1 + port: 6379 + password: redis_Hkhtz7 + database: 4 + + # ---------- MinIO ---------- + minio: + host: 192.168.1.1 + port: 9001 + accessKey: BHB4X5zgZXuzQE6ku31e + secretKey: NZgHX5LhtAVD1KdaucL0EVWy6IQk4owdZLA2A7wi + bucketName: health-bucket + downloadDir: /data/excel + + # ---------- XXL-JOB ---------- + xxlJob: + addresses: http://192.168.1.88:8800/xxl-job-admin + accessToken: xxljobTokenQ + address: 192.168.1.1:9999 + ip: 192.168.1.1 + port: 9998 + + # ---------- Eureka ---------- + eureka: + host: 192.168.1.1 + port: 8761 + + # ---------- DB(应用内复用) ---------- + db: + host: 192.168.1.80 + port: 3728 + user: root + password: Khq#P9hZ4L@EwCZw + database: health_ai_b \ No newline at end of file diff --git a/db.py b/db.py new file mode 100644 index 0000000..ede5b27 --- /dev/null +++ b/db.py @@ -0,0 +1,41 @@ +import pymysql +from config import DB + +def get_conn(): + return pymysql.connect( + **DB, + cursorclass=pymysql.cursors.DictCursor + ) + +def get_table(table_name): + sql = """ + SELECT table_name, table_comment + FROM information_schema.tables + WHERE table_schema=%s AND table_name=%s + """ + with get_conn() as conn: + with conn.cursor() as cur: + cur.execute(sql, (DB["database"], table_name)) + return cur.fetchone() + +def get_columns(table_name): + sql = """ + SELECT column_name, data_type, column_comment + FROM information_schema.columns + WHERE table_schema=%s AND table_name=%s + ORDER BY ordinal_position + """ + with get_conn() as conn: + with conn.cursor() as cur: + cur.execute(sql, (DB["database"], table_name)) + return cur.fetchall() + +def mysql_to_java(mysql_type): + mapping = { + "bigint": "Long", + "int": "Integer", + "varchar": "String", + "datetime": "LocalDateTime", + "decimal": "BigDecimal" + } + return mapping.get(mysql_type, "String") diff --git a/generator.py b/generator.py new file mode 100644 index 0000000..81f9acd --- /dev/null +++ b/generator.py @@ -0,0 +1,312 @@ +import logging +import os +from datetime import datetime +from jinja2 import Environment, FileSystemLoader +# from config import * +from db import get_table, get_columns +from utils import * +import argparse +import yaml + +env = Environment(loader=FileSystemLoader("templates")) + +def build_fields(table_name): + columns = get_columns(table_name) + fields = [] + for c in columns: + fields.append({ + "java_name": to_camel(c["column_name"]), + "tab_name": c["column_name"], + "tab_type": c["data_type"], + "java_get_name": to_m_camel(c["column_name"]), + "java_type": mysql_to_java(c["data_type"]), + "comment": c["column_comment"] + }) + return fields + +def render(template_name, out_path, context, overwrite=False): + """ + :param template_name: 模板文件名 + :param out_path: 输出文件路径 + :param context: 渲染上下文 + :param overwrite: 是否覆盖已存在文件,默认 False + """ + # 文件存在且不允许覆盖 → 直接跳过 + if os.path.exists(out_path) and not overwrite: + logging.info("Skip exists file: %s", out_path) + return + + tpl = env.get_template(template_name) + content = tpl.render(**context) + + os.makedirs(os.path.dirname(out_path), exist_ok=True) + with open(out_path, "w", encoding="utf-8") as f: + f.write(content) + + logging.info("Generated file: %s", out_path) + +def generate(table_names: list[str], model_names: list[str], conf_name: str, over_write: bool): + + # context = { + # "mainModule": MAIN_MODULE, + # "moduleName": MODULE_NAME, + # "groupId": GROUP_ID, + # "author": AUTHOR, + # "wechat": WECHAT, + # "date": datetime.now().strftime("%Y-%m-%d"), + # "entityLombokModel": True, + # "package": { + # "Base": BASE_PACKAGE, + # "Common": f"{BASE_PACKAGE}.{DEFAULT_PREFIX_URL}.common", + # "Entity": f"{BASE_PACKAGE}.{DEFAULT_PREFIX_URL}.entity", + # "Service": f"{BASE_PACKAGE}.{DEFAULT_PREFIX_URL}.service", + # "Controller": f"{BASE_PACKAGE}.{DEFAULT_PREFIX_URL}.controller", + # "ServiceImpl": f"{BASE_PACKAGE}.{DEFAULT_PREFIX_URL}.service.impl", + # "Mapper": f"{BASE_PACKAGE}.{DEFAULT_PREFIX_URL}.mapper" + # }, + # "db": DB, + # "application": APPLICATION, + # "restControllerStyle": REST_CONTROLLER_STYLE + # } + + with open(conf_name, "r", encoding="utf-8") as f: + cfg = yaml.safe_load(f) + cfg = resolve_config(cfg) + + context = { + "mainModule": cfg["mainModule"], + "moduleName": cfg["moduleName"], + "groupId": cfg["groupId"], + "author": cfg["author"], + "wechat": cfg["wechat"], + "date": datetime.now().strftime("%Y-%m-%d"), + "entityLombokModel": cfg["entityLombokModel"], + "package": cfg["package"], + "db": cfg["db"], + "application": cfg["application"], + "restControllerStyle": cfg["restControllerStyle"] + } + # MAIN_BASE_PACKAGE_DIR = f"{OUTPUT_DIR}/java/{to_path(BASE_PACKAGE)}" + # MAIN_OUTPUT_DIR = f"{MAIN_BASE_PACKAGE_DIR}/{DEFAULT_PREFIX_URL}" + + BASE_DIR = cfg["baseDir"] + BASE_PACKAGE = cfg["package"]["Base"] + OUTPUT_DIR = cfg["outputDir"] + MAIN_MODULE = cfg["mainModule"] + MODULE_NAME = cfg["moduleName"] + MAIN_BASE_PACKAGE_DIR = f"{cfg['outputDir']}/java/{to_path(BASE_PACKAGE)}" + MAIN_OUTPUT_DIR = f"{MAIN_BASE_PACKAGE_DIR}/{cfg['package']['Models']}" + + + # ========= 按表循环 ========= + for table_name in table_names: + table = get_table(table_name) + entity = to_class(table_name) + + context = dict(context) + context.update({ + "fields": build_fields(table_name), + "table": { + "entity": entity, + "lowerEntity": lower_first(entity), + "name": table_name, + "comment": table["table_comment"] + } + }) + + # ========= 需要循环生成的模板 ========= + render( + "entity.java.j2", + f"{BASE_DIR}{MAIN_OUTPUT_DIR}/entity/{entity}.java", + context, + over_write + ) + + render( + "controller.java.j2", + f"{BASE_DIR}{MAIN_OUTPUT_DIR}/controller/{entity}Controller.java", + context, + over_write + ) + + render( + "service.java.j2", + f"{BASE_DIR}{MAIN_OUTPUT_DIR}/service/{entity}Service.java", + context, + over_write + ) + + render( + "serviceImpl.java.j2", + f"{BASE_DIR}{MAIN_OUTPUT_DIR}/service/impl/{entity}MPJBaseServiceImpl.java", + context, + over_write + ) + + render( + "mapper.java.j2", + f"{BASE_DIR}{MAIN_OUTPUT_DIR}/mapper/{entity}Mapper.java", + context, + over_write + ) + + render( + "mapper.xml.j2", + f"{MAIN_MODULE}/{MODULE_NAME}/src/main/resources/mappers/{entity}Mapper.xml", + context, + over_write + ) + + # ========= 生成固定模板 ========= + # BaseEntity + render( + "baseEntity.java.j2", + f"{BASE_DIR}{MAIN_OUTPUT_DIR}/entity/BaseEntity.java", + context + ) + + # common MybatisPlusConfig + render( + "mybatisPlusConfig.java.j2", + f"{BASE_DIR}{MAIN_OUTPUT_DIR}/common/config/MybatisPlusConfig.java", + context + ) + + # common MybatisPlusConfig + render( + "webLogAspect.java.j2", + f"{BASE_DIR}{MAIN_OUTPUT_DIR}/common/config/WebLogAspect.java", + context + ) + + # common 基础输出result + render( + "result.java.j2", + f"{BASE_DIR}{MAIN_OUTPUT_DIR}/common/vo/Result.java", + context + ) + + #Util 公共功能 + for file in ["Md5HashUtil.java.j2","FilesUtil.java.j2"]: + render( + file, + f"{BASE_DIR}{MAIN_OUTPUT_DIR}/common/unit/{file.replace('.j2', '')}", + context + ) + + # application 启动的方法 + render( + "application.java.j2", + f"{BASE_DIR}{MAIN_BASE_PACKAGE_DIR}/Application.java", + context + ) + + # test 测试类 + render( + "applicationTests.java.j2", + f"{BASE_DIR}{OUTPUT_DIR}/test/{to_path(BASE_PACKAGE)}/ApplicationTests.java", + context + ) + + # 主pom文件 + render( + "main.pom.xml.j2", + f"{BASE_DIR}{MAIN_MODULE}/pom.xml", + context + ) + + # 子项目pom文件 + render( + "project.pom.xml.j2", + f"{BASE_DIR}{MAIN_MODULE}/{MODULE_NAME}/pom.xml", + context + ) + + #项目的yml配置文件 resources 生成环境配置为了最低限度能将项目跑起来 + render( + "application.yml.j2", + f"{MAIN_MODULE}/{MODULE_NAME}/src/main/resources/application.yml", + context + ) + + #项目开发环境的yml配置文件 resources yml 只生成dev环境配置为了最低限度能将项目跑起来 + render( + "application-dev.yml.j2", + f"{MAIN_MODULE}/{MODULE_NAME}/src/main/resources/application-dev.yml", + context + ) + + #项目开发环境的yml配置文件 resources yml 只生成dev环境配置为了最低限度能将项目跑起来 + render( + "logback.xml.j2", + f"{MAIN_MODULE}/{MODULE_NAME}/src/main/resources/logback.xml", + context + ) + + # ========= 功能模块 ========= + for model_name in model_names: + match model_name: + case "swagger": + # common Swagger2 + render( + "swagger2.java.j2", + f"{BASE_DIR}{MAIN_OUTPUT_DIR}/common/config/Swagger2.java", + context + ) + case "saToken": + # common GlobalException soToken 报错自定义 + render( + "globalException.java.j2", + f"{BASE_DIR}{MAIN_OUTPUT_DIR}/common/config/GlobalException.java", + context + ) + render( + "saTokenConfigure.java.j2", + f"{BASE_DIR}{MAIN_OUTPUT_DIR}/common/config/SaTokenConfig.java", + context + ) + case "minio": + #MinioConfig + render( + "MinioConfig.java.j2", + f"{BASE_DIR}{MAIN_OUTPUT_DIR}/common/config/MinioConfig.java", + context + ) + render( + "MinioUpController.java.j2", + f"{BASE_DIR}{MAIN_OUTPUT_DIR}/controller/MinioUpController.java", + context + ) + render( + "MinioUpComponent.java.j2", + f"{BASE_DIR}{MAIN_OUTPUT_DIR}/common/unit/MinioUpComponent.java", + context + ) + case "xxlJob": + # common XxlJobConfig + render( + "xxlJobConfig.java.j2", + f"{BASE_DIR}{MAIN_OUTPUT_DIR}/common/config/XxlJobConfig.java", + context + ) + # common xxjob的测试类 + render( + "testJob.java.j2", + f"{BASE_DIR}{MAIN_OUTPUT_DIR}/common/job/TestJob.java", + context + ) + +if __name__ == "__main__": + args = parse_args() + + tables = [t.strip() for t in args.tab.split(",") if t.strip()] + models = [m.strip() for m in args.model.split(",") if m.strip()] + conf = args.conf + re = args.re + + generate( + table_names=tables, + model_names=models, + conf_name=conf, + over_write=re + ) \ No newline at end of file diff --git a/templates/FilesUtil.java.j2 b/templates/FilesUtil.java.j2 new file mode 100644 index 0000000..e8b27f4 --- /dev/null +++ b/templates/FilesUtil.java.j2 @@ -0,0 +1,142 @@ +package {{ package.Common }}.unit; + +import org.apache.commons.io.FileUtils; +import org.springframework.web.multipart.MultipartFile; +import sun.misc.BASE64Encoder; + +import java.io.*; +import java.util.Base64; + +public class FilesUtil { + + /** + * 根据文件路径获取文件字节流 + * @return + * filePath 文件路径 + * @throws IOException + */ + public static byte[] toByteArray(String filePath) throws IOException { + File f = new File(filePath); + if (!f.exists()) { + throw new FileNotFoundException("文件不存在"); + } + + ByteArrayOutputStream bos = new ByteArrayOutputStream((int) f.length()); + BufferedInputStream in = null; + try { + in = new BufferedInputStream(new FileInputStream(f)); + int buf_size = 1024; + byte[] buffer = new byte[buf_size]; + int len = 0; + while (-1 != (len = in.read(buffer, 0, buf_size))) { + bos.write(buffer, 0, len); + } + return bos.toByteArray(); + } catch (IOException e) { + e.printStackTrace(); + throw e; + } finally { + try { + in.close(); + } catch (IOException e) { + e.printStackTrace(); + } + bos.close(); + } + } + public static File multipartFileToFile(MultipartFile file) throws Exception { + File toFile = null; + if (file.equals("") || file.getSize() <= 0) { + file = null; + } else { + InputStream ins = null; + ins = file.getInputStream(); + toFile = new File(file.getOriginalFilename()); + inputStreamToFile(ins, toFile); + ins.close(); + } + return toFile; + + } + + + + private static void inputStreamToFile(InputStream ins, File file) { + try { + OutputStream os = new FileOutputStream(file); + int bytesRead = 0; + byte[] buffer = new byte[8192]; + while ((bytesRead = ins.read(buffer, 0, 8192)) != -1) { + os.write(buffer, 0, bytesRead); + } + os.close(); + ins.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static String TransformPhotoToBase64Data(String path){ + Base64.Encoder encoder= Base64.getEncoder(); //获取Base64编码器 + byte [] ImgContainer = null ; //数据集缓存器 + FileInputStream fileInputStream = null; //文件输入流 + try { + System.out.println(path); + File file=new File(path); + fileInputStream = new FileInputStream(file); //到指定路径寻找文件 + ImgContainer = new byte[fileInputStream.available()]; //设置图片字节数据缓冲区大小 + fileInputStream.read(ImgContainer); //将数据流中的图片数据读进缓冲区 + String Base64ImgData =encoder.encodeToString(ImgContainer); //将图片编码转换成Base64格式的数据集 + fileInputStream.close(); //关闭数据流 + return Base64ImgData; //将缓冲区数据转换成字符数据返回 + } catch (FileNotFoundException e) { + return "找不到指定文件!"; + } catch (IOException e) { + e.printStackTrace(); + } + return "null"; + } + + public static String getBase64String(MultipartFile multiPartFile) throws IOException { + String baseStr = null; + + //把MultipartFile转化为File + File file = new File(multiPartFile.getOriginalFilename()); + FileUtils.copyInputStreamToFile(multiPartFile.getInputStream(), file); + + try {//file转base64 + FileInputStream inputStream = new FileInputStream(file); + byte[] buffer = new byte[(int) file.length()]; + inputStream.read(buffer); + inputStream.close(); + baseStr = new BASE64Encoder().encode(buffer); + + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + //删除临时文件 + if (file.exists()) { + file.delete(); + } + baseStr = baseStr.replaceAll("\r\n", ""); + return baseStr; + } + + /** + * 返回文件扩展名,带。 + * @param file + * @return + */ + public static String getFileExtension(MultipartFile file) { + String fileName = file.getOriginalFilename(); + int dotIndex = fileName.lastIndexOf("."); + if (dotIndex == -1) { + return ""; + } else { + return "."+fileName.substring(dotIndex + 1); + } + } + +} diff --git a/templates/Md5HashUtil.java.j2 b/templates/Md5HashUtil.java.j2 new file mode 100644 index 0000000..3c98768 --- /dev/null +++ b/templates/Md5HashUtil.java.j2 @@ -0,0 +1,21 @@ +package {{ package.Common }}.unit; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class Md5HashUtil { + + public static String getMD5Hash(byte[] input) { + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] digest = md.digest(input); + StringBuilder sb = new StringBuilder(); + for (byte b : digest) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("Could not find MD5 algorithm", e); + } + } +} diff --git a/templates/MinioConfig.java.j2 b/templates/MinioConfig.java.j2 new file mode 100644 index 0000000..3129ee5 --- /dev/null +++ b/templates/MinioConfig.java.j2 @@ -0,0 +1,84 @@ +package {{ package.Common }}.config; +import {{ package.Common }}.unit.MinioUpComponent; + + +import io.minio.MinioClient; +import lombok.Data; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; +import java.io.Serializable; + +/** + * @Description: minio配置类 + * @Author: {{author}} + * @Date: {{date}} + * @wechat: {{ wechat }} + */ + +@Component +@Data +@ConditionalOnClass(MinioUpComponent.class) +public class MinioConfig implements Serializable { + + + /** + * API调用地址ip + */ + @Value("${minio.config.ip}") + private String ip; + + /** + * API调用地址端口 + */ + @Value("${minio.config.port}") + private String port; + + /** + * 连接账号 + */ + @Value("${minio.config.accessKey}") + private String accessKey; + + /** + * 连接秘钥 + */ + @Value("${minio.config.secretKey}") + private String secretKey; + + /** + * minio存储桶的名称 + */ + @Value("${minio.config.bucketName}") + private String bucketName; + + /** + * 文件下载到本地的路径 + */ + @Value("${minio.config.downloadDir}") + private String downloadDir; + + /** + * #如果是true,则用的是https而不是http,默认值是true + */ + @Value("${minio.config.secure}") + private Boolean secure; + + @Value("${minio.config.readPath}") + private String readPath; + + @Value("${minio.config.endpoint}") + private String endpoint; + + @Bean + public MinioClient buildClient() { + //1.创建minio链接客户端 + return MinioClient + .builder() + .credentials(accessKey, secretKey) + .endpoint(endpoint) + .build(); + } + +} diff --git a/templates/MinioUpComponent.java.j2 b/templates/MinioUpComponent.java.j2 new file mode 100644 index 0000000..0bee741 --- /dev/null +++ b/templates/MinioUpComponent.java.j2 @@ -0,0 +1,191 @@ +package {{ package.Common }}.unit; +import {{ package.Common }}.config.MinioConfig; + +import io.minio.*; +import io.minio.http.Method; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.concurrent.TimeUnit; + + +/**** + * @Description: 上传 文件组件 + * @Author: {{author}} + * @Date: {{date}} + * @Wechat: {{ wechat }} + */ +@Slf4j +@Component +public class MinioUpComponent { + + private final static String separator = "/"; + @Autowired + private MinioConfig minioConfig; + @Autowired + private MinioClient minioClient; + + /** + * @param dirPath + * @param filename yyyy/mm/dd/file.jpg + * @return + */ + public static String builderFilePath(String dirPath, String filename) { + StringBuilder stringBuilder = new StringBuilder(50); + if (!StringUtils.isEmpty(dirPath)) { + stringBuilder.append(dirPath).append(separator); + } + SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd"); + String todayStr = sdf.format(new Date()); + stringBuilder.append(todayStr).append(separator); + stringBuilder.append(filename); + return stringBuilder.toString(); + } + + /** + * 上传图片文件 + * + * @param prefix 文件前缀 + * @param filename 文件名 + * @param inputStream 文件流 + * @return 文件全路径 + */ + public String uploadImg(String prefix, String filename, InputStream inputStream) { + String filePath = builderFilePath(prefix, filename); + try { + PutObjectArgs putObjectArgs = PutObjectArgs.builder() + .object(filePath) + .contentType("image/jpg") + .bucket(minioConfig.getBucketName()).stream(inputStream, inputStream.available(), -1) + .build(); + minioClient.putObject(putObjectArgs); + StringBuilder urlPath = new StringBuilder(minioConfig.getReadPath()); + urlPath.append(separator + minioConfig.getBucketName()); + urlPath.append(separator); + urlPath.append(filePath); + return urlPath.toString(); + } catch (Exception ex) { + log.error("minio put file error.", ex); + throw new RuntimeException("上传文件失败"); + } + } + + /** + * 上传html文件 + * + * @param prefix 文件前缀 + * @param filename 文件名 + * @param inputStream 文件流 + * @return 文件全路径 + */ + public String uploadHtml(String prefix, String filename, InputStream inputStream) { + String filePath = builderFilePath(prefix, filename); + try { + PutObjectArgs putObjectArgs = PutObjectArgs.builder() + .object(filePath) + .contentType("text/html") + .bucket(minioConfig.getBucketName()) + .stream(inputStream, inputStream.available(), -1) + .build(); + minioClient.putObject(putObjectArgs); + StringBuilder urlPath = new StringBuilder(minioConfig.getReadPath()); + urlPath.append(separator + minioConfig.getBucketName()); + urlPath.append(separator); + urlPath.append(filePath); + return urlPath.toString(); + } catch (Exception ex) { + log.error("minio put file error.", ex); + ex.printStackTrace(); + throw new RuntimeException("上传文件失败"); + } + } + + /** + * 防盗链接有效期按分钟 + * + * @param filePath 文件前缀 + * @param expiry 超时分钟 + * @return 文件全路径 + */ + public String getPresignObjectUrl(String filePath, Integer expiry) { + try { + filePath = filePath.replace("http://health.reglory.com.cn:9001/health-bucket/", ""); + GetPresignedObjectUrlArgs putObjectArgs = GetPresignedObjectUrlArgs.builder() + .object(filePath) + .bucket(minioConfig.getBucketName()) + .method(Method.GET) +// .region("health.reglory.com.cn:9001") + .expiry(expiry, TimeUnit.MINUTES) + .build(); + return minioClient.getPresignedObjectUrl(putObjectArgs); + } catch (Exception ex) { + log.error("minio put file error.", ex); + ex.printStackTrace(); + throw new RuntimeException("上传文件失败"); + } + } + + /** + * 删除文件 + * + * @param pathUrl 文件全路径 + */ + public void delete(String pathUrl) { + String key = pathUrl.replace(minioConfig.getEndpoint() + "/", ""); + int index = key.indexOf(separator); + String bucket = key.substring(0, index); + String filePath = key.substring(index + 1); + // 删除Objects + RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder().bucket(bucket).object(filePath).build(); + try { + minioClient.removeObject(removeObjectArgs); + } catch (Exception e) { + log.error("minio remove file error. pathUrl:{}", pathUrl); + e.printStackTrace(); + } + } + + + /** + * 下载文件 + * + * @param pathUrl 文件全路径 + * @return 文件流 + */ + public byte[] downLoadFile(String pathUrl) { + String key = pathUrl.replace(minioConfig.getEndpoint() + "/", ""); + int index = key.indexOf(separator); + //String bucket = key.substring(0, index); + String filePath = key.substring(index + 1); + InputStream inputStream = null; + try { + inputStream = minioClient.getObject( + GetObjectArgs.builder().bucket(minioConfig.getBucketName()).object(filePath).build() + ); + } catch (Exception e) { + log.error("minio down file error. pathUrl:{}", pathUrl); + e.printStackTrace(); + } + + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + byte[] buff = new byte[100]; + int rc = 0; + while (true) { + try { + if (!((rc = inputStream.read(buff, 0, 100)) > 0)) break; + } catch (IOException e) { + e.printStackTrace(); + } + byteArrayOutputStream.write(buff, 0, rc); + } + return byteArrayOutputStream.toByteArray(); + } +} + diff --git a/templates/MinioUpController.java.j2 b/templates/MinioUpController.java.j2 new file mode 100644 index 0000000..239ef04 --- /dev/null +++ b/templates/MinioUpController.java.j2 @@ -0,0 +1,53 @@ +package {{ package.Controller }}; +import {{ package.Common }}.unit.FilesUtil; +import {{ package.Common }}.unit.Md5HashUtil; +import {{ package.Common }}.unit.MinioUpComponent; +import {{ package.Common }}.vo.Result; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; + +/** + *

+ * 上传控制器 + *

+ * + * @author tiger + * @since 2024-06-04 + */ +@Api(tags = "文件上传") +@RestController +@CrossOrigin(origins = "*") +@RequestMapping("/models/file") +public class MinioUpController { + + @Autowired + private MinioUpComponent minioUpComponent; + + @ApiOperation(value = "图片上传") + @CrossOrigin(origins = "*") + @PostMapping(value = "/upImg") + public Result up(@RequestParam("file") MultipartFile file) { + if (file.isEmpty()) { + return Result.error("请选择一个文件上传"); + } + // 文件上传路径 可以为空 默认 bucketName: "health-bucket" 桶的名字为根目录 + // String path = "/tmp/user"; + String path = ""; + String url = ""; + try { + url = minioUpComponent.uploadImg(path,Md5HashUtil.getMD5Hash(file.getBytes()) + + FilesUtil.getFileExtension(file), file.getInputStream()); + } catch (IOException e) { + return Result.error("上传失败"); + } + return Result.OK(url); + } + + +} diff --git a/templates/application-dev.yml.j2 b/templates/application-dev.yml.j2 new file mode 100644 index 0000000..fd7e5ca --- /dev/null +++ b/templates/application-dev.yml.j2 @@ -0,0 +1,137 @@ +server: + port: 8060 + servlet: + context-path: /api + +spring: +############## datasource 相关 + datasource: + type: com.zaxxer.hikari.HikariDataSource + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://{{ application.db.host }}:{{ application.db.port }}/{{ application.db.database }}?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&serverTimezone=Asia/Shanghai&useSSL=false + username: {{ application.db.user }} + password: {{ application.db.password }} + #Hikari连接池配置 + hikari: + #池中维护的最小空闲连接数 + minimum-idle: 5 + #池中最大连接数,包括闲置和使用中的连接 + maximum-pool-size: 15 + #自动提交从池中返回的连接 + auto-commit: true + #连接允许在池中闲置的最长时间 + idle-timeout: 30000 + #连接池的用户定义名称,主要出现在日志记录和JMX管理控制台中以识别池和池配置 + pool-name: DatebookHikariCP + #池中连接最长生命周期 + max-lifetime: 18000000 + #等待来自池的连接的最大毫秒数 + connection-timeout: 30000 + #验证该连接是否是有效的查询语句 + connection-test-query: select 1 from dual + cloud: + inetutils: + timeout-seconds: 10 + config: + enabled: false +############## redis 相关 + redis: + host: {{ application.redis.host }} + port: {{ application.redis.port }} + password: {{ application.redis.password }} + timeout: 10000 + jedis: + pool: + max-active: 1000 + max-wait: -1ms + max-idle: 10 + min-idle: 5 + database: {{application.redis.database}} +############## eureka 相关 +eureka: + instance: + instance-id: ${spring.cloud.client.ip-address}:${server.port} + prefer-ip-address: true + client: + healthcheck: + enabled: true + service-url: + defaultZone: http://{{ application.eureka.host }}:{{ application.eureka.port }}/eureka + +############## mybatis-plus配置 +mybatis-plus: + # 启动检查MyBatis配置文件 + check-config-location: false + # MyBatis配置文件位置 + config-location: + # MyBaits别名包扫描路径 + type-aliases-package: com.qiangesoft.mybatisplusjoin.entity + # Mapper所对应的XML文件位置 默认【classpath*:/mapper/**/*.xml】 + mapper-locations: classpath*:/mapper/*Mapper.xml + # TypeHandler扫描路径 + type-handlers-package: + configuration: + # 日志打印 + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + # 是否开启自动驼峰命名规则 + map-underscore-to-camel-case: true + # 开启Mybatis二级缓存,默认为true + cache-enabled: true + global-config: + # 控制台mybatis-plus的logo + banner: true + db-config: + # 全局默认主键类型 + id-type: auto + # 逻辑删除配置 + logic-delete-field: deleted + logic-delete-value: 1 + logic-not-delete-value: 0 + +############## Sa-Token 配置 (文档: https://sa-token.cc) ############## +sa-token: + # token 名称(同时也是 cookie 名称) + token-name: Token + # token 有效期(单位:秒) 默认30天,-1 代表永久有效 + timeout: 2592000 + # token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结 + active-timeout: -1 + # 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录) + is-concurrent: true + # 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token) + is-share: true + # token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik) + token-style: uuid + # 是否输出操作日志 + is-log: true + +############## swagger +swagger: + show: true + +############## minio +minio: + config: + ip: {{ application.minio.host }} #ip地址 + port: {{ application.minio.port }} # 端口号 + accessKey: {{ application.minio.accessKey }} # 账号 + secretKey: {{ application.minio.secretKey }} # 密码 + secure: false #如果是true,则用的是https而不是http,默认值是true + bucketName: "{{ application.minio.bucketName }}" # 桶的名字 + downloadDir: "{{ application.minio.downloadDir }}" #保存到本地的路径 + readPath: "http://{{ application.minio.host }}:{{ application.minio.port }}" #保存到本地的路径 + endpoint: "http://{{ application.minio.host }}:{{ application.minio.port }}" #保存到本地的路径 + +############## xxl-job +xxl: + job: + admin: + addresses: {{ application.xxlJob.addresses }} # 调度中心部署根地址 + accessToken: "{{ application.xxlJob.accessToken }}" # 执行器通讯TOKEN,非空时启用 + executor: + appname: xxl-{{ application.name }} # 执行器AppName + address: {{ application.xxlJob.address }} # 执行器注册地址,为空时使用内嵌服务IP:PORT + ip: {{ application.xxlJob.ip }} # 执行器IP,默认为空自动获取 + port: {{ application.xxlJob.port }} # 执行器端口号 + logpath: /data/applogs/xxl-job/jobhandler # 执行器运行日志文件存储磁盘路径 + logretentiondays: 30 # 执行器日志文件保存天数 \ No newline at end of file diff --git a/templates/application.java.j2 b/templates/application.java.j2 new file mode 100644 index 0000000..02116bc --- /dev/null +++ b/templates/application.java.j2 @@ -0,0 +1,48 @@ +package {{ package.Base }}; + +//-- 固定引入 --// +import com.fasterxml.jackson.core.JsonProcessingException; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.Bean; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; +//-- 固定引入 --// + +/** + * @Description: 启动类 + * @Author: {{author}} + * @Date: {{date}} + * @Wechat: {{ wechat }} + */ +@SpringBootApplication +@EnableDiscoveryClient +@EnableFeignClients +@EnableCaching +public class Application { + + public static void main(String[] args) throws JsonProcessingException { + SpringApplication.run(Application.class, args); + } + + + + @Bean + public CorsFilter corsFilter() { + final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + final CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); // 允许cookies跨域 + config.addAllowedOrigin("*");// 允许向该服务器提交请求的URI,*表示全部允许。。这里尽量限制来源域,比如http://xxxx:8080 ,以降低安全风险。。 + config.addAllowedHeader("*");// 允许访问的头信息,*表示全部 + config.setMaxAge(18000L);// 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了 + config.addAllowedMethod("*");// 允许提交请求的方法,*表示全部允许,也可以单独设置GET、PUT等 + source.registerCorsConfiguration("/**", config); + return new CorsFilter(source); + } + + +} diff --git a/templates/application.yml.j2 b/templates/application.yml.j2 new file mode 100644 index 0000000..5e65a7f --- /dev/null +++ b/templates/application.yml.j2 @@ -0,0 +1,26 @@ +spring: + profiles: + active: dev #开发环境 + #active: test #测试环境 + #active: pro #生产环境 + application: + name: {{ application.name }} + devtools: + restart: + log-condition-evaluation-delta: false + security: + user: + name: admin + password: admin + servlet: + multipart: + max-file-size: 30MB + max-request-size: 30MB +logging: + level: + root: info + {{ application.package.base }}: debug + org: + springframework: + boot: + autoconfigure: error diff --git a/templates/applicationTests.java.j2 b/templates/applicationTests.java.j2 new file mode 100644 index 0000000..097a810 --- /dev/null +++ b/templates/applicationTests.java.j2 @@ -0,0 +1,23 @@ +package {{ package.Base }}; + + /*** + * @description {{ projectName }} + * @version 1.0.0 + * @Author: {{author}} + * @Date: {{date}} + * @Wechat: {{ wechat }} + */ + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import java.io.IOException; + +@SpringBootTest +class ApplicationTests { + + @Test + void contextLoads() throws IOException { + System.out.println("test start"); + } + +} diff --git a/templates/baseEntity.java.j2 b/templates/baseEntity.java.j2 new file mode 100644 index 0000000..ef85f60 --- /dev/null +++ b/templates/baseEntity.java.j2 @@ -0,0 +1,86 @@ +package {{ package.Entity }}; + +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; +import java.util.Date; + +/** + * 基础Entity类 + * @Author: {{author}} + * @Date: {{date}} + * @Wechat: {{ wechat }} + */ + +@ApiModel("基础Entity类") +@Setter +@Getter +public class BaseEntity implements Serializable { + + @ApiModelProperty(value = "主键id") + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @ApiModelProperty(value = "创建者", hidden = true) + protected Integer createdBy; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @ApiModelProperty(value = "创建日期", hidden = true, example = "yyyy-MM-dd HH:mm:ss") + @TableField(value = "created_at", fill = FieldFill.INSERT) + protected Date createdAt; + + @ApiModelProperty(value = "更新者", hidden = true) + protected Integer updatedBy; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @ApiModelProperty(value = "更新日期", hidden = true, example = "yyyy-MM-dd HH:mm:ss") + @TableField(value = "updated_at", fill = FieldFill.INSERT, update = "NOW()") + protected Date updatedAt; + + @ApiModelProperty(value = "删除标记", hidden = true) + @TableField(value = "deleted_flag") + @TableLogic(value = "0", delval = "1") + protected Integer deletedFlag; + + @ApiModelProperty(value = "页码", required = false) + @TableField(exist = false) + private Integer pageNum; //页码 + + @ApiModelProperty(value = "每页条数", required = false) + @TableField(exist = false) + private Integer pageSize;//每页条数 + + @ApiModelProperty(value = "排序方式排序[true:正序; false:倒序]", required = false) + @TableField(exist = false) + private Boolean sort; + + @ApiModelProperty(value = "排序字段,参照返回字段", required = false) + @TableField(exist = false) + private String sortName; + + @ApiModelProperty(value = "用户ID") + @TableField(value = "user_id") + private Integer userId; + + @ApiModelProperty(value = "创建者用户名") + @TableField(exist = false) + private String createdByName; + + @ApiModelProperty(value = "更新者用户名") + @TableField(exist = false) + private String updatedByName; + + public BaseEntity() { + if (this.pageSize == null) { + this.setPageSize(10); + } + if (this.pageNum == null) { + this.setPageNum(1); + } + } +} diff --git a/templates/controller.java.j2 b/templates/controller.java.j2 new file mode 100644 index 0000000..2a5c1cf --- /dev/null +++ b/templates/controller.java.j2 @@ -0,0 +1,107 @@ +package {{ package.Controller }}; +import {{ package.Entity }}.{{ table.entity }}; +import {{ package.Service }}.{{ table.entity }}Service; +import {{ package.Common }}.vo.Result; + +//--- import 固定引入 ---// +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.validation.Valid; +import java.util.List; +//--- import 固定引入 ---// + +/** + *

+ * {{ table.comment }} 前端控制器 + *

+ * @Author: {{author}} + * @Date: {{date}} + * @Wechat: {{ wechat }} + */ +@Api(tags = "{{ table.comment }}") +{% if restControllerStyle %} +@RestController +{% else %} +@Controller +{% endif %} +@RequestMapping("{{ table.name }}") +public class {{ table.entity }}Controller { + + @Resource + private {{ table.entity }}Service {{table.lowerEntity}}Service; + + @ApiOperation(value = "{{ table.comment }}分页列表查询", response = {{ table.entity }}.class) + @PostMapping(value = "/page") + public Result> page(@Valid @RequestBody {{ table.entity }} param,@RequestHeader("token") String token, + @RequestHeader(value = "version", defaultValue = "1.0") String version) { + Page<{{ table.entity }}> page = {{table.lowerEntity}}Service.page(param); + return Result.OK(page); + } + + + @ApiOperation(value = "{{ table.comment }}根据条件查询") + @PostMapping(value = "/info") + public Result info(@Valid @RequestBody {{ table.entity }} param, + @RequestHeader("token") String token, + @RequestHeader(value = "version", defaultValue = "1.0") String version) { + if (token ==null) { + return Result.error("token不能为空"); + } + {{ table.entity }} data = {{table.lowerEntity}}Service.info(param); + return Result.OK(data); + } + + + @ApiOperation(value = "{{ table.comment }}新增") + @PostMapping(value = "/add") + public Result add(@Valid @RequestBody {{ table.entity }} param, + @RequestHeader("token") String token, + @RequestHeader(value = "version", defaultValue = "1.0") String version) { + if (token ==null) { + return Result.error("token不能为空"); + } + {{table.lowerEntity}}Service.add(param); + return Result.OK(); + } + + @ApiOperation(value = "{{ table.comment }}修改") + @PostMapping(value = "/modify") + public Result modify(@Valid @RequestBody {{ table.entity }} param, + @RequestHeader("token") String token, + @RequestHeader(value = "version", defaultValue = "1.0") String version) { + if (token ==null) { + return Result.error("token不能为空"); + } + {{table.entity}} info = {{table.lowerEntity}}Service.info(Integer.valueOf(param.getId())); + if (info ==null) { + return Result.error(String.format("[%s]记录不存在", info)); + } + {{table.lowerEntity}}Service.modify(param); + return Result.OK(); + } + + @ApiOperation(value = "{{ table.comment }}删除(单个条目)") + @GetMapping(value = "/remove/{id}") + public Result remove(@PathVariable Integer id, + @RequestHeader("token") String token, + @RequestHeader(value = "version", defaultValue = "1.0") String version) { + + {{table.lowerEntity}}Service.remove(id); + return Result.OK(); + } + + @ApiOperation(value = "{{ table.comment }}删除(多个条目)") + @PostMapping(value = "/removes") + public Result removes(@Valid @RequestBody List ids, + @RequestHeader("token") String token, + @RequestHeader(value = "version", defaultValue = "1.0") String version) { + {{table.lowerEntity}}Service.removes(ids); + return Result.OK(); + } + + +} diff --git a/templates/entity.java.j2 b/templates/entity.java.j2 new file mode 100644 index 0000000..50e777e --- /dev/null +++ b/templates/entity.java.j2 @@ -0,0 +1,40 @@ +package {{ package.Entity }}; +import {{ package.Entity }}.BaseEntity; + +//--- import 固定引入 ---// +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.io.Serializable; +import java.util.Date; + + +/** + * {{ table.comment }} + * + * @Author: {{author}} + * @Date: {{date}} + * @Wechat: {{ wechat }} + */ + +{% if entityLombokModel %} +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("{{ table.name }}") +@ApiModel(value = "{{ table.name }}对象", description = "{{ table.comment }}"){% endif %} +public class {{ table.entity }} extends BaseEntity { + + private static final long serialVersionUID = 1L; +{% for field in fields %} + {% if field.java_type == 'Date' and field.tab_type == 'date' %}@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8"){% endif %} + {% if field.java_type == 'Date' and field.tab_type == 'datetime' %}@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8"){% endif %} + @TableField("{{ field.tab_name }}") + @ApiModelProperty("{{ field.comment }}") + private {{ field.java_type }} {{ field.java_name }}; +{% endfor %} + + +} diff --git a/templates/globalException.java.j2 b/templates/globalException.java.j2 new file mode 100644 index 0000000..841796f --- /dev/null +++ b/templates/globalException.java.j2 @@ -0,0 +1,74 @@ +package {{ package.Common }}.config; +import {{ package.Common }}.vo.Result; + +//--- 固定引入 ---// +import cn.dev33.satoken.exception.*; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +//--- 固定引入 ---// + +/** + * 全局异常处理 + * @Author: {{author}} + * @Date: {{date}} + * @Wechat: {{ wechat }} + */ + + +@RestControllerAdvice +public class GlobalException { + + // 拦截:未登录异常 + @ExceptionHandler(NotLoginException.class) + public Result handlerException(NotLoginException e) { + + // 打印堆栈,以供调试 + e.printStackTrace(); + + // 返回给前端 + return Result.error(1000,e.getMessage()); + } + + // 拦截:缺少权限异常 + @ExceptionHandler(NotPermissionException.class) + public Result handlerException(NotPermissionException e) { + e.printStackTrace(); + return Result.error("缺少权限:" + e.getPermission()); + } + + // 拦截:缺少角色异常 + @ExceptionHandler(NotRoleException.class) + public Result handlerException(NotRoleException e) { + e.printStackTrace(); + return Result.error("缺少角色:" + e.getRole()); + } + + // 拦截:二级认证校验失败异常 + @ExceptionHandler(NotSafeException.class) + public Result handlerException(NotSafeException e) { + e.printStackTrace(); + return Result.error("二级认证校验失败:" + e.getService()); + } + + // 拦截:服务封禁异常 + @ExceptionHandler(DisableServiceException.class) + public Result handlerException(DisableServiceException e) { + e.printStackTrace(); + return Result.error("当前账号 " + e.getService() + " 服务已被封禁 (level=" + e.getLevel() + "):" + e.getDisableTime() + "秒后解封"); + } + + // 拦截:Http Basic 校验失败异常 + @ExceptionHandler(NotBasicAuthException.class) + public Result handlerException(NotBasicAuthException e) { + e.printStackTrace(); + return Result.error(1003,e.getMessage()); + } + + // 拦截:其它所有异常 + @ExceptionHandler(Exception.class) + public Result handlerException(Exception e) { + e.printStackTrace(); + return Result.error(1002,e.getMessage()); + } + +} \ No newline at end of file diff --git a/templates/logback.xml.j2 b/templates/logback.xml.j2 new file mode 100644 index 0000000..d9ed0f8 --- /dev/null +++ b/templates/logback.xml.j2 @@ -0,0 +1,48 @@ + + + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + + + + + + + ${LOG_HOME}/TestWeb.log.%d{yyyy-MM-dd}.log + + 30 + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + + + + 10MB + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/templates/main.pom.xml.j2 b/templates/main.pom.xml.j2 new file mode 100644 index 0000000..7d4a452 --- /dev/null +++ b/templates/main.pom.xml.j2 @@ -0,0 +1,229 @@ + + + 4.0.0 + {{ groupId }} + {{ mainModule }} + 1.0.0 + ${project.artifactId} + pom + + {{ moduleName }} + + + + 2.0.8.RELEASE + Finchley.SR4 + Cairo-SR7 + UTF-8 + 1.8 + 1.8 + 2.0.5 + 4.5.0 + 11.2.0.4.0-atlassian-hosted + 1.0 + 5.22.0 + 0.0.9 + 2.0.0 + 2.10.0 + 1.7 + 4.1.0 + 2.1.0 + 4.11 + 1.33 + 2.1.5 + 2.9.8 + 2.4.1 + 6.0.0-GA + 192.168.0.13:5000 + 1.3.2 + 1.1.6 + 1.2.78 + 2.1 + 9.1.5 + 1.6.1 + 3.5.3.2 + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + io.spring.platform + platform-bom + ${spring-platform.version} + pom + import + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + com.fasterxml.jackson.module + jackson-modules-java8 + ${jackson.modules} + pom + import + + + + + + + + org.springframework.cloud + spring-cloud-starter-sleuth + + + org.aspectj + aspectjweaver + + + org.apache.skywalking + apm-toolkit-logback-1.x + ${skywalking.version} + + + + org.springframework.boot + spring-boot-starter-actuator + + + + de.codecentric + spring-boot-admin-starter-client + ${spring-boot-admin.version} + + + + org.springframework.cloud + spring-cloud-starter-netflix-hystrix + + + + org.postgresql + postgresql + + + + net.logstash.logback + logstash-logback-encoder + ${logstash.version} + + + + + de.schlichtherle.truelicense + truelicense-core + ${truelicense.version} + + + + org.projectlombok + lombok + 1.18.24 + provided + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.apache.shiro + shiro-spring + ${shiro.version} + + + + com.alibaba + druid + ${druid.version} + + + com.alibaba + fastjson + ${fastjson.version} + + + + net.sf.barcode4j + barcode4j + ${barcode4j.version} + + + + org.xhtmlrenderer + flying-saucer-pdf + ${flying-saucer-pdf.version} + + + + org.apache.axis2 + axis2 + ${axis2.version} + + + com.baomidou + mybatis-plus-boot-starter + ${mybatisplus.version} + + + + org.apache.poi + poi-ooxml + 3.9 + + + + + ${project.name} + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + ${project.build.finalName} + + + + + repackage + + + + + + + + + maven-compiler-plugin + 3.8.0 + + ${maven.compiler.target} + ${maven.compiler.source} + UTF-8 + + + + pl.project13.maven + git-commit-id-plugin + 2.2.5 + + + + \ No newline at end of file diff --git a/templates/mapper.java.j2 b/templates/mapper.java.j2 new file mode 100644 index 0000000..f94bff0 --- /dev/null +++ b/templates/mapper.java.j2 @@ -0,0 +1,20 @@ +package {{ package.Mapper }}; + +import {{package.Entity}}.{{ table.entity }}; + +//--- import 固定引入 ---// +import com.github.yulichang.base.MPJBaseMapper; +//--- import 固定引入 ---// + +/** + *

+ * 操作记录 Mapper 接口 + *

+ * + * @Author: {{author}} + * @Date: {{date}} + * @Wechat: {{ wechat }} + */ +public interface {{ table.entity }}Mapper extends MPJBaseMapper<{{ table.entity }}> { + +} \ No newline at end of file diff --git a/templates/mapper.xml.j2 b/templates/mapper.xml.j2 new file mode 100644 index 0000000..76b62b5 --- /dev/null +++ b/templates/mapper.xml.j2 @@ -0,0 +1,11 @@ + + + + + + {% for field in fields %} + + {% endfor %} + + + diff --git a/templates/mybatis-config.xml b/templates/mybatis-config.xml new file mode 100644 index 0000000..5127c3d --- /dev/null +++ b/templates/mybatis-config.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/templates/mybatisPlusConfig.java.j2 b/templates/mybatisPlusConfig.java.j2 new file mode 100644 index 0000000..d4f8eb2 --- /dev/null +++ b/templates/mybatisPlusConfig.java.j2 @@ -0,0 +1,37 @@ +package {{ package.Common }}.config; + +//--- 固定引入 ---// +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +//--- 固定引入 ---// + +/** + * MybatisPlus 配置 + * + * @Author: {{author}} + * @Date: {{date}} + * @Wechat: {{ wechat }} + */ + +@Configuration +@MapperScan(basePackages = {"{{package.Mapper}}"}) +public class MybatisPlusConfig { + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + //向Mybatis过滤器链中添加分页拦截器 + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); + //还可以添加i他的拦截器 + return interceptor; + } + + @Bean + public ConfigurationCustomizer configurationCustomizer() { + return configuration -> configuration.setUseGeneratedShortKey(false); + } +} diff --git a/templates/project.pom.xml.j2 b/templates/project.pom.xml.j2 new file mode 100644 index 0000000..f9ac7bf --- /dev/null +++ b/templates/project.pom.xml.j2 @@ -0,0 +1,310 @@ + + + 4.0.0 + + {{ groupId }} + {{ mainModule }} + 1.0.0 + + {{ moduleName }} + {{ moduleName }} + + 1.8 + + + + + + io.netty + netty-all + + + + + mysql + mysql-connector-java + 8.0.27 + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-tomcat + + + + org.springframework.boot + spring-boot-starter-jdbc + + + + + com.github.yulichang + mybatis-plus-join-boot-starter + 1.4.11 + + + + com.github.yulichang + mybatis-plus-join-core + 1.4.11 + + + + io.minio + minio + 7.1.0 + + + + + com.jcraft + jsch + + + + + org.freemarker + freemarker + 2.3.28 + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + org.apache.commons + commons-pool2 + 2.10.0 + + + + com.alibaba + fastjson + 1.2.78 + + + + org.apache.axis + axis + 1.4 + + + + org.apache.axis + axis-jaxrpc + 1.4 + compile + + + + com.sap.cloud.db.jdbc + ngdbc + 2.5.49 + pom + test + + + + commons-discovery + commons-discovery + 0.2 + + + + wsdl4j + wsdl4j + 1.6.3 + + + + org.junit.jupiter + junit-jupiter-api + 5.5.0 + test + + + + cn.hutool + hutool-all + 5.3.8 + + + + com.alibaba + transmittable-thread-local + 2.12.2 + + + + org.jeecgframework + autopoi + 1.3.6 + + + + io.springfox + springfox-swagger2 + 2.9.2 + + + + io.springfox + springfox-swagger-ui + 2.9.2 + + + + com.github.xiaoymin + swagger-bootstrap-ui + 1.8.7 + + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + + + org.springframework.boot + spring-boot-devtools + 2.0.4.RELEASE + true + + + + commons-net + commons-net + 3.6 + + + + + org.apache.poi + poi + 3.9 + + + + org.apache.poi + poi-ooxml + 3.9 + + + + org.springframework.boot + spring-boot-starter-websocket + + + + com.github.penggle + kaptcha + 2.3.2 + + + + + com.aliyun + aliyun-java-sdk-core + 4.5.17 + + + + com.aliyun + aliyun-java-sdk-iot + 7.1.0 + + + + + + com.baomidou + mybatis-plus-generator + 3.5.3.2 + + + + com.baomidou + mybatis-plus-boot-starter + 3.5.3.2 + + + + + org.modelmapper + modelmapper + 2.3.9 + + + + + com.github.xiaoymin + swagger-bootstrap-ui + 1.8.7 + compile + + + + + cn.dev33 + sa-token-redis-jackson + 1.38.0 + + + + cn.dev33 + sa-token-spring-boot-starter + 1.38.0 + compile + + + + + com.xuxueli + xxl-job-core + 2.4.0 + + + + + com.github.whvcse + easy-captcha + 1.6.2 + + + + + org.springframework.boot + spring-boot-starter-websocket + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + + + diff --git a/templates/result.java.j2 b/templates/result.java.j2 new file mode 100644 index 0000000..bb8b650 --- /dev/null +++ b/templates/result.java.j2 @@ -0,0 +1,172 @@ +package {{ package.Common }}.vo; + +//---固定引入---// +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import java.io.Serializable; +//---固定引入---// + + +/** + * 接口返回数据格式 + * @Author: {{author}} + * @Date: {{date}} + * @Wechat: {{ wechat }} + */ + +@Data +@ApiModel(value="接口返回对象", description="接口返回对象") +public class Result implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 成功标志 + */ + @ApiModelProperty(value = "成功标志") + private boolean success = true; + + /** + * 返回处理消息 + */ + @ApiModelProperty(value = "返回处理消息") + private String message = ""; + + /** + * 返回代码 + */ + @ApiModelProperty(value = "返回代码") + private Integer code = 0; + + /** + * 返回数据对象 data + */ + @ApiModelProperty(value = "返回数据对象") + private T result; + + /** + * 时间戳 + */ + @ApiModelProperty(value = "时间戳") + private long timestamp = System.currentTimeMillis(); + + public Result() { + } + + /** + * 兼容VUE3版token失效不跳转登录页面 + * @param code + * @param message + */ + public Result(Integer code, String message) { + this.code = code; + this.message = message; + } + + public Result success(String message) { + this.message = message; + this.code = 200; + this.success = true; + return this; + } + + @Deprecated + public static Result ok() { + Result r = new Result(); + r.setSuccess(true); + r.setCode(200); + return r; + } + + @Deprecated + public static Result ok(String msg) { + Result r = new Result(); + r.setSuccess(true); + r.setCode(200); + r.setMessage(msg); + return r; + } + + @Deprecated + public static Result ok(Object data) { + Result r = new Result(); + r.setSuccess(true); + r.setCode(200); + r.setResult(data); + return r; + } + + public static Result OK() { + Result r = new Result(); + r.setSuccess(true); + r.setCode(200); + return r; + } + + public static Result OK(String msg) { + Result r = new Result(); + r.setSuccess(true); + r.setCode(200); + r.setMessage(msg); + //Result OK(String msg)方法会造成兼容性问题 issues/I4IP3D + r.setResult((T) msg); + return r; + } + + public static Result OK(T data) { + Result r = new Result(); + r.setSuccess(true); + r.setCode(200); + r.setResult(data); + return r; + } + + public static Result OK(String msg, T data) { + Result r = new Result(); + r.setSuccess(true); + r.setCode(200); + r.setMessage(msg); + r.setResult(data); + return r; + } + + public static Result error(String msg, T data) { + Result r = new Result(); + r.setSuccess(false); + r.setCode(500); + r.setMessage(msg); + r.setResult(data); + return r; + } + + public static Result error(String msg) { + return error(500, msg); + } + + public static Result error(int code, String msg) { + Result r = new Result(); + r.setCode(code); + r.setMessage(msg); + r.setSuccess(false); + return r; + } + + public Result error500(String message) { + this.message = message; + this.code = 500; + this.success = false; + return this; + } + /** + * 无权限访问返回结果 + */ + public static Result noauth(String msg) { + return error(210, msg); + } + + @JsonIgnore + private String onlTable; + +} \ No newline at end of file diff --git a/templates/saTokenConfigure.java.j2 b/templates/saTokenConfigure.java.j2 new file mode 100644 index 0000000..b1b2764 --- /dev/null +++ b/templates/saTokenConfigure.java.j2 @@ -0,0 +1,46 @@ +package {{ package.Common }}.config; + +import cn.dev33.satoken.interceptor.SaInterceptor; +import cn.dev33.satoken.router.SaRouter; +import cn.dev33.satoken.stp.StpUtil; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import javax.annotation.Resource; +import java.util.List; + +/** + * SaToken配置 + * @Author: {{author}} + * @Date: {{date}} + * @Wechat: {{ wechat }} + * 解决@RequestAttribute、@RequestParam和@RequestBody三种类型的时间类型参数接收与转换问题 + */ +@Configuration +public class SaTokenConfig implements WebMvcConfigurer { + + @Override + public void addInterceptors(InterceptorRegistry registry) { + + // 注册 Sa-Token 拦截器,定义详细认证规则 + registry.addInterceptor(new SaInterceptor(handler -> { + // 指定一条 match 规则 + SaRouter + .match("/models/**") // 拦截的 path 列表,可以写多个 */ + // .notMatch("/models/health-user/login") // 排除掉的 path 列表,可以写多个 + .check(r -> StpUtil.checkLogin()); // 要执行的校验动作,可以写完整的 lambda 表达式 + + //拦截并写操作日志 + // SaRouter.match(r).check(t->{ + // SaLog.info("操作日志:{}", SaLog.getParamJson()); + // SaLog.info("操作日志:{}", SaLog.getRequestBody()); + // ); + + })).addPathPatterns("/**"); + } + + +} diff --git a/templates/service.java.j2 b/templates/service.java.j2 new file mode 100644 index 0000000..2dd83b5 --- /dev/null +++ b/templates/service.java.j2 @@ -0,0 +1,85 @@ +package {{ package.Service }}; +import {{ package.Entity }}.{{ table.entity }}; + +//--- import 固定引入 ---// +import com.baomidou.mybatisplus.extension.service.IService; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import java.util.List; +//--- import 固定引入 ---// + +/** + * {{ table.comment }}服务接口 + * + * @author {{ author }} + * @date {{ date }} + */ + +public interface {{ table.entity }}Service extends IService<{{ table.entity }}> { + + /** + * {{ table.comment }}分页列表 + * + * @param param 根据需要进行传值 + * @return + */ + Page<{{ table.entity }}> page({{ table.entity }} param); + + /** + * {{ table.comment }}详情 + * + * @param param + * @return + */ + {{ table.entity }} info({{ table.entity }} param); + + + /** + * {{ table.comment }}详情 + * + * @param id + * @return + */ + {{ table.entity }} info(Integer id); + + + /** + * {{ table.comment }}列表 + * + * @param param + * @return + */ + List<{{ table.entity }}> list({{ table.entity }} param); + + /** + * {{ table.comment }}新增 + * + * @param param 根据需要进行传值 + * @return + */ + void add({{ table.entity }} param); + + /** + * {{ table.comment }}修改 + * + * @param param 根据需要进行传值 + * @return + */ + void modify({{ table.entity }} param); + + /** + * {{ table.comment }}删除(单个条目) + * + * @param id + * @return + */ + void remove(Integer id); + + + /** + * {{ table.comment }}删除(多个条目) + * + * @param ids + * @return + */ + void removes(List ids); +} diff --git a/templates/serviceImpl.java.j2 b/templates/serviceImpl.java.j2 new file mode 100644 index 0000000..1a1f659 --- /dev/null +++ b/templates/serviceImpl.java.j2 @@ -0,0 +1,154 @@ +package {{ package.ServiceImpl }}; + +import {{ package.Entity }}.{{ table.entity }}; +import {{ package.Mapper }}.{{ table.entity }}Mapper; +import {{ package.Service }}.{{ table.entity }}Service; + +//--- import 固定引入 ---// +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.github.yulichang.base.MPJBaseServiceImpl; +import com.github.yulichang.wrapper.MPJLambdaWrapper; +import org.springframework.stereotype.Service; +import org.apache.commons.lang3.StringUtils; +import java.util.Date; +import java.util.List; +//--- import 固定引入 ---// + +/** + *

+ * {{ table.comment }} 服务实现类 + *

+ * + * @Author: {{author}} + * @Date: {{date}} + * @Wechat: {{ wechat }} + */ +@Service +public class {{ table.entity }}MPJBaseServiceImpl extends MPJBaseServiceImpl<{{ table.entity }}Mapper, {{ table.entity }}> implements {{ table.entity }}Service { + + /** + * {{ table.comment }}分页列表 + * @param param 根据需要进行传值 + * @return + */ + @Override + public Page<{{ table.entity }}> page({{ table.entity }} param) { + MPJLambdaWrapper<{{ table.entity }}> queryWrapper = new MPJLambdaWrapper<>(); + queryWrapper.selectAll({{ table.entity }}.class) + {% for field in fields %}{% if not entityLombokModel %}{% if field.java_type == "Boolean" %}{% set get_pre_fix = "is" %}{% else %}{% set get_pre_fix = "get" %}{% endif %}{% if field.java_type == "String" %} + .eq(StringUtils.isNotBlank(param.{{ get_pre_fix }}{{ field.java_get_name }}()), {{ table.entity }}::{{ get_pre_fix }}{{ field.capital_name }}, param.{{ get_pre_fix }}{{ field.java_get_name }}()) + {% else %} + .eq(param.{{ get_pre_fix }}{{ field.java_get_name }}() != null, {{ table.entity }}::{{ get_pre_fix }}{{ field.capital_name }}, param.{{ get_pre_fix }}{{ field.java_get_name }}()) + {% endif %}{% else %}{% if field.java_type == "String" %} + .eq(StringUtils.isNotBlank(param.get{{ field.java_get_name }}()), {{ table.entity }}::get{{ field.java_get_name }}, param.get{{ field.java_get_name }}()) + {% else %} + .eq(param.get{{ field.java_get_name }}() != null, {{ table.entity }}::get{{ field.java_get_name }}, param.get{{ field.java_get_name }}()) + {% endif %}{% endif %}{% endfor %}; + return selectJoinListPage( new Page<>(param.getPageNum(), param.getPageSize()), {{ table.entity }}.class, queryWrapper); + } + + + /** + * {{ table.comment }}详情 + * @param param + * @return + */ + @Override + public {{ table.entity }} info({{ table.entity }} param) { + MPJLambdaWrapper<{{ table.entity }}> queryWrapper = new MPJLambdaWrapper<>(); + queryWrapper.selectAll({{ table.entity }}.class) + {% for field in fields %}{% if not entityLombokModel %}{% if field.java_type == "Boolean" %}{% set get_pre_fix = "is" %}{% else %}{% set get_pre_fix = "get" %}{% endif %}{% if field.java_type == "String" %} + .eq(StringUtils.isNotBlank(param.{{ get_pre_fix }}{{ field.java_get_name }}()), {{ table.entity }}::{{ get_pre_fix }}{{ field.capital_name }}, param.{{ get_pre_fix }}{{ field.java_get_name }}()) + {% else %} + .eq(param.{{ get_pre_fix }}{{ field.java_get_name }}() != null, {{ table.entity }}::{{ get_pre_fix }}{{ field.capital_name }}, param.{{ get_pre_fix }}{{ field.java_get_name }}()) + {% endif %}{% else %}{% if field.java_type == "String" %} + .eq(StringUtils.isNotBlank(param.get{{ field.java_get_name }}()), {{ table.entity }}::get{{ field.java_get_name }}, param.get{{ field.java_get_name }}()) + {% else %} + .eq(param.get{{ field.java_get_name }}() != null, {{ table.entity }}::get{{ field.java_get_name }}, param.get{{ field.java_get_name }}()) + {% endif %}{% endif %}{% endfor %}; + return selectJoinOne( {{ table.entity }}.class, queryWrapper ); + } + + + + /** + * {{ table.comment }}详情 + * @param id + * @return + */ + @Override + public {{ table.entity }} info(Integer id) { + return getById(id); + } + + + /** + * {{ table.comment }}列表 + * @param param + */ + @Override + public List<{{ table.entity }}> list({{ table.entity }} param) { + MPJLambdaWrapper<{{ table.entity }}> queryWrapper = new MPJLambdaWrapper<>(); + queryWrapper.selectAll({{ table.entity }}.class) + {% for field in fields %}{% if not entityLombokModel %}{% if field.java_type == "Boolean" %}{% set get_pre_fix = "is" %}{% else %}{% set get_pre_fix = "get" %}{% endif %}{% if field.java_type == "String" %} + .eq(StringUtils.isNotBlank(param.{{ get_pre_fix }}{{ field.java_get_name }}()), {{ table.entity }}::{{ get_pre_fix }}{{ field.capital_name }}, param.{{ get_pre_fix }}{{ field.java_get_name }}()) + {% else %} + .eq(param.{{ get_pre_fix }}{{ field.java_get_name }}() != null, {{ table.entity }}::{{ get_pre_fix }}{{ field.capital_name }}, param.{{ get_pre_fix }}{{ field.java_get_name }}()) + {% endif %}{% else %}{% if field.java_type == "String" %} + .eq(StringUtils.isNotBlank(param.get{{ field.java_get_name }}()), {{ table.entity }}::get{{ field.java_get_name }}, param.get{{ field.java_get_name }}()) + {% else %} + .eq(param.get{{ field.java_get_name }}() != null, {{ table.entity }}::get{{ field.java_get_name }}, param.get{{ field.java_get_name }}()) + {% endif %}{% endif %}{% endfor %}; + return selectJoinList( {{ table.entity }}.class, queryWrapper); + } + + + /** + * {{ table.comment }}新增 + * + * @param param 根据需要进行传值 + * @return + */ + @Override + public void add({{ table.entity }} param) { + param.setCreatedAt(new Date()); + param.setCreatedBy(param.getUserId()); + save(param); + } + + /** + * {{ table.comment }}修改 + * + * @param param 根据需要进行传值 + * @return + */ + @Override + public void modify({{ table.entity }} param) { + param.setUpdatedAt(new Date()); + param.setUpdatedBy(param.getUserId()); + updateById(param); + } + + /** + * {{ table.comment }}删除(单个条目) + * + * @param id + * @return + */ + @Override + public void remove(Integer id) { + removeById(id); + } + + /** + * {{ table.comment }}删除(多个条目) + * + * @param ids + * @return + */ + @Override + public void removes(List ids) { + removeByIds(ids); + } +} diff --git a/templates/swagger2.java.j2 b/templates/swagger2.java.j2 new file mode 100644 index 0000000..da9debd --- /dev/null +++ b/templates/swagger2.java.j2 @@ -0,0 +1,66 @@ +package {{ package.Common }}.config; + +//--- 固定引入 ---// +import com.github.xiaoymin.swaggerbootstrapui.annotations.EnableSwaggerBootstrapUI; +import com.google.common.base.Predicates; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.service.Contact; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2; +//--- 固定引入 ---// + + +/** + * Swagger2配置类 + * 在与spring boot集成时,放在与Application.java同级的目录下。 + * 通过@Configuration注解,让Spring来加载该类配置。 + * 再通过@EnableSwagger2注解来启用Swagger2。 + * @Author: {{author}} + * @Date: {{date}} + * @Wechat: {{ wechat }} + * 访问地址:http://localhost:port/api/doc.html + */ +@Configuration +@EnableSwagger2 +@EnableSwaggerBootstrapUI +public class Swagger2 { + + @Value("${swagger.show}") + private boolean swaggerShow; + + @Bean + public Docket createRestApi() { + + return new Docket(DocumentationType.SWAGGER_2) + .apiInfo(apiInfo()) + .enable(swaggerShow)// 设置正式环境不显示Swagger2 + .select() + // 配置多个扫描路径 + .apis( + Predicates.or( + RequestHandlerSelectors.basePackage("{{package.Controller}}") + ) + ) + .paths(PathSelectors.any()) + .build(); + } + + private ApiInfo apiInfo() { + // name:作者,url:通常项目地址,email:邮箱 + Contact contact=new Contact("{{author}}","https://blog.csdn.net/Extraordinarylife"," "); + return new ApiInfoBuilder() + .title("数据中心接口文档")//标题 + .description("数据中心相关接口文档")// 描述 + .contact(contact) + .version("1.0")//版本 + .build(); + } +} + diff --git a/templates/testJob.java.j2 b/templates/testJob.java.j2 new file mode 100644 index 0000000..ad2d458 --- /dev/null +++ b/templates/testJob.java.j2 @@ -0,0 +1,26 @@ +package {{ package.Common }}.job; + +import com.xxl.job.core.handler.annotation.XxlJob; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + + +/** + * @Description: + * @Author: {{author}} + * @Date: {{date}} + * @Wechat: {{ wechat }} + */ +@Component +public class TestJob { + private static Logger logger = LoggerFactory.getLogger(TestJob.class); + + @XxlJob("testJobHand") + public void testJobHand() { + logger.info(">>>>>>>>>>> testJobHand"); + } +} + + + diff --git a/templates/webLogAspect.java.j2 b/templates/webLogAspect.java.j2 new file mode 100644 index 0000000..b8daefa --- /dev/null +++ b/templates/webLogAspect.java.j2 @@ -0,0 +1,85 @@ +package {{ package.Common }}.config; + +//--- 固定引入 ---// +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.annotation.RestController; +import javax.servlet.http.HttpServletRequest; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +//--- 固定引入 ---// + + +/*** + * @Description: 日志切面 + * @Author: {{author}} + * @Date: {{date}} + * @Wechat: {{ wechat }} + */ +@Slf4j +@Aspect +@Component +public class WebLogAspect { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + /** 拦截所有 Controller 方法 */ + @Around("within(@org.springframework.web.bind.annotation.RestController *)") + public Object logWebRequest(ProceedingJoinPoint joinPoint) throws Throwable { + long start = System.currentTimeMillis(); + + ServletRequestAttributes attributes = + (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + HttpServletRequest request = attributes != null ? attributes.getRequest() : null; + + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + String methodName = signature.toShortString(); + + // ========== 读取请求入参 ========== + String paramsJson = ""; + try { + paramsJson = objectMapper.writeValueAsString(joinPoint.getArgs()); + } catch (Exception ignored) {} + + // ----------- 打印入参 ------- + if (request != null) { + log.info("\n================= 请求开始 =================\n" + + "URL : {}\n" + + "Method : {}\n" + + "Controller : {}\n" + + "IP : {}\n" + + "Request : {}\n" + + "============================================", + request.getRequestURI(), + request.getMethod(), + methodName, + request.getRemoteAddr(), + paramsJson); + } + + // ========== 执行方法 ========== + Object result = joinPoint.proceed(); + + // ========== 打印返回结果 ========== + String resultJson = ""; + try { + resultJson = objectMapper.writeValueAsString(result); + } catch (Exception ignored) {} + + log.info("\n================= 请求结束 =================\n" + + "URL : {}\n" + + "耗时 : {} ms\n" + + "返回值 : {}\n" + + "============================================", + request != null ? request.getRequestURI() : methodName, + (System.currentTimeMillis() - start), + resultJson); + + return result; + } +} diff --git a/templates/xxlJobConfig.java.j2 b/templates/xxlJobConfig.java.j2 new file mode 100644 index 0000000..d91f945 --- /dev/null +++ b/templates/xxlJobConfig.java.j2 @@ -0,0 +1,51 @@ +package {{ package.Common }}.config; + +import com.xxl.job.core.executor.impl.XxlJobSpringExecutor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/**** + * xxl-job config + * @Author: {{author}} + * @Date: {{date}} + * @Wechat: {{ wechat }} + */ +@Configuration +public class XxlJobConfig implements WebMvcConfigurer { + + @Value("${xxl.job.admin.addresses}") + private String adminAddresses; + + @Value("${xxl.job.executor.appname}") + private String appName; + + @Value("${xxl.job.executor.ip}") + private String ip; + + @Value("${xxl.job.executor.port}") + private int port; + + @Value("${xxl.job.accessToken}") + private String accessToken; + + @Value("${xxl.job.executor.logpath}") + private String logPath; + + @Value("${xxl.job.executor.logretentiondays}") + private int logRetentionDays; + + @Bean + public XxlJobSpringExecutor xxlJobExecutor() { + XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor(); + xxlJobSpringExecutor.setAdminAddresses(adminAddresses); + xxlJobSpringExecutor.setAppname(appName); + xxlJobSpringExecutor.setIp(ip); + xxlJobSpringExecutor.setPort(port); + xxlJobSpringExecutor.setAccessToken(accessToken); + xxlJobSpringExecutor.setLogPath(logPath); + xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays); + return xxlJobSpringExecutor; + } +} diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..cda26ec --- /dev/null +++ b/utils.py @@ -0,0 +1,131 @@ +import argparse +import re +from copy import deepcopy + +def to_camel(name): + parts = name.split("_") + return parts[0] + "".join(p.capitalize() for p in parts[1:]) + +def to_m_camel(name): + return "".join(p.capitalize() for p in name.split("_")) + +def to_class(name): + return "".join(p.capitalize() for p in name.split("_")) + +def lower_first(s: str) -> str: + if not s: + return s + return s[0].lower() + s[1:] + +def to_path(name): + parts = name.split(".") + if len(parts) < 2: # 如果没有".",parts长度就是1 + return "" + return parts[0] + "/" + "/".join(p for p in parts[1:]) + + +def get_first_part(name, default=""): + delimiter="_" + if delimiter not in name: + return default + + parts = name.split(delimiter) + if not parts or not parts[0]: + return default + + return parts[0].capitalize() + +def parse_args(): + parser = argparse.ArgumentParser(description="Java Code Generator") + + parser.add_argument( + "--tab", + default="", + help="表名,多个用逗号分隔,例如: tab,tab2" + ) + + parser.add_argument( + "--model", + default="", + help="可选模块 xxlJob,minio,saToken,swagger" + ) + + parser.add_argument( + "--re", + action="store_true", + help="是否覆盖已存在文件(默认不覆盖)" + ) + + parser.add_argument( + "--conf", + required=True, + default="", + help="配置文件路径" + ) + + return parser.parse_args() + + +VAR_PATTERN = re.compile(r"\$\{([^}]+)\}") +def get_by_path(data: dict, path: str): + """ + 从 dict 中通过 a.b.c 取值 + """ + cur = data + for key in path.split("."): + if not isinstance(cur, dict) or key not in cur: + return None + cur = cur[key] + return cur + +def resolve_string(value: str, data: dict) -> str: + def replacer(match): + expr = match.group(1) + v = get_by_path(data, expr) + return str(v) if v is not None else match.group(0) + + return VAR_PATTERN.sub(replacer, value) + +def resolve_config(config: dict, max_rounds=5) -> dict: + """ + 递归解析配置中的 ${xxx} + """ + result = deepcopy(config) + + for _ in range(max_rounds): + changed = False + + def walk(obj): + nonlocal changed + if isinstance(obj, dict): + for k, v in obj.items(): + obj[k] = walk(v) + elif isinstance(obj, list): + return [walk(i) for i in obj] + elif isinstance(obj, str): + new = resolve_string(obj, result) + if new != obj: + changed = True + return new + return obj + + walk(result) + + if not changed: + break + + return result + + +def mysql_to_java(mysql_type): + mapping = { + "bigint": "Long", + "int": "Integer", + "tinyint": "Integer", + "varchar": "String", + "mediumtext": "String", + "datetime": "Date", + "date": "Date", + "decimal": "BigDecimal" + } + return mapping.get(mysql_type, "String")