spring boot 2.6.0
mybatis-plus 3.5.9
swagger-ui 3.0.0
openapi-ui 1.8.0
``
idea 创建一个 maven webapp 空项目,手动配置 spring mvc
教程
| <dependencies> |
| <!--springMVC坐标--> |
| <dependency> |
| <groupId>org.springframework</groupId> |
| <artifactId>spring-webmvc</artifactId> |
| <version>5.1.5.RELEASE</version> |
| </dependency> |
| <!--servlet坐标--> |
| <dependency> |
| <groupId>javax.servlet</groupId> |
| <artifactId>javax.servlet-api</artifactId> |
| <version>3.1.0</version> |
| <scope>provided</scope> |
| </dependency> |
| <!--jsp坐标--> |
| <dependency> |
| <groupId>javax.servlet.jsp</groupId> |
| <artifactId>jsp-api</artifactId> |
| <version>2.2</version> |
| <scope>provided</scope> |
| </dependency> |
| </dependencies> |
| |
配置 spring boot
| <parent> |
| <artifactId>spring-boot-starter-parent</artifactId> |
| <groupId>org.springframework.boot</groupId> |
| <version>2.5.0</version> |
| </parent> |
| |
| <dependencies> |
| <!-- Spring MVC依赖 --> |
| <dependency> |
| <groupId>org.springframework.boot</groupId> |
| <artifactId>spring-boot-starter-web</artifactId> |
| </dependency> |
| <!-- springBoot的Test依赖 --> |
| <dependency> |
| <groupId>org.springframework.boot</groupId> |
| <artifactId>spring-boot-starter-test</artifactId> |
| </dependency> |
| </dependencies> |
| |
| <properties> |
| <maven.compiler.source>11</maven.compiler.source> |
| <maven.compiler.target>11</maven.compiler.target> |
| <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
| </properties> |
| |
| <build> |
| <plugins> |
| <plugin> |
| <groupId>org.apache.tomcat.maven</groupId> |
| <artifactId>tomcat7-maven-plugin</artifactId> |
| <version>2.1</version> |
| </plugin> |
| </plugins> |
| </build> |
| |
配置 myBatis
教程
教程
手动配置 swagger3 docs
教程
教程
访问swagger界面报错汇总
配置 切面AOP,获取每次api调用的类和参数
| @Aspect |
| @Component |
| public class ApiLoggingAspect { |
| private static final Logger logger = LoggerFactory.getLogger(ApiLoggingAspect.class); |
| |
| |
| @Pointcut("execution(* com.booksSystem.controller..*(..))") |
| public void controllerMethods() { |
| } |
| |
| |
| @Before("controllerMethods()") |
| public void logMethodParameters(JoinPoint joinPoint) { |
| MethodSignature signature = (MethodSignature) joinPoint.getSignature(); |
| String methodName = signature.getName(); |
| Object[] args = joinPoint.getArgs(); |
| HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); |
| String httpMethod = request.getMethod(); |
| String className = signature.getDeclaringTypeName(); |
| logger.info("Request: {} - {}: {} - Params: {}", httpMethod,className,methodName,Arrays.toString(args)); |
| } |
| |
| |
| @AfterReturning("controllerMethods()") |
| public void logClassNameAndMethodName(JoinPoint joinPoint) { |
| MethodSignature signature = (MethodSignature) joinPoint.getSignature(); |
| logger.info("Request Success"); |
| } |
| } |
| |
mysql 创建数据库,创建数据表
| CREATE DATABASE library_system |
| CHARACTER SET utf8 |
| COLLATE utf8_general_ci; |
| |
| |
| |
| |
| |
| DROP TABLE IF EXISTS `books`; |
| CREATE TABLE `books` ( |
| `id` bigint(0) NOT NULL AUTO_INCREMENT, |
| `book_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '图书名称', |
| `book_price` decimal(10, 2) NULL DEFAULT NULL COMMENT '图书价格', |
| `is_delete` int(0) NULL DEFAULT NULL COMMENT '软删除', |
| PRIMARY KEY (`id`) USING BTREE |
| ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; |
| |
配置 mybatis-plus 软删除
| |
| mybatis-plus: |
| global-config: |
| db-config: |
| logic-delete-value: 1 |
| logic-not-delete-value: 0 |
| |
| |
| @TableLogic |
| private Integer deleted; |
| |
| import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; |
| import org.springframework.context.annotation.Bean; |
| import org.springframework.context.annotation.Configuration; |
| import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; |
| |
| @Configuration |
| public class MybatisPlusConfig { |
| |
| @Bean |
| public MybatisPlusInterceptor mybatisPlusInterceptor() { |
| MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); |
| |
| interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); |
| return interceptor; |
| } |
| } |
| |
mysql 练习
基本的语句
where、order by asc|desc、like、limit、group by、union、is null、is not null、
sql函数
round、avg、sum等
mybatis-plus 3.5.6 下配置 swagger-ui 3x 风格代码生成
CodeGenerator.java
| package com.booksSystem.codeGenerator; |
| |
| import com.baomidou.mybatisplus.generator.FastAutoGenerator; |
| import com.baomidou.mybatisplus.generator.config.OutputFile; |
| import com.baomidou.mybatisplus.generator.config.rules.DbColumnType; |
| import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; |
| |
| import java.sql.Types; |
| import java.util.Collections; |
| |
| |
| |
| |
| |
| |
| |
| public class CodeGenerator { |
| public static void main(String[] args) { |
| FastAutoGenerator.create("jdbc:mysql://localhost:3306/l_test?useUnicode=true&characterEncoding=utf-8", "root", "lamberts") |
| .globalConfig(builder -> { |
| builder.author("lambert") |
| .enableSwagger() |
| .outputDir("/Users/lantian/Documents/code/java/library_system/code/"); |
| }) |
| .dataSourceConfig(builder -> |
| builder.typeConvertHandler((globalConfig, typeRegistry, metaInfo) -> { |
| int typeCode = metaInfo.getJdbcType().TYPE_CODE; |
| if (typeCode == Types.SMALLINT) { |
| |
| return DbColumnType.INTEGER; |
| } |
| return typeRegistry.getColumnType(metaInfo); |
| }) |
| ) |
| .packageConfig(builder -> |
| builder.parent("com.booksSystem") |
| .moduleName("") |
| .pathInfo(Collections.singletonMap(OutputFile.xml, "/Users/lantian/Documents/code/java/library_system/code/xml/")) |
| ) |
| .strategyConfig(builder -> |
| builder |
| .addInclude("approval_departmental") |
| .addTablePrefix("") |
| .entityBuilder().enableFileOverride().enableLombok().enableTableFieldAnnotation() |
| .javaTemplate("/templates/entity.java") |
| .serviceBuilder().enableFileOverride() |
| .serviceTemplate("templates/service.java") |
| .serviceImplTemplate("templates/serviceImpl.java") |
| .mapperBuilder().enableFileOverride() |
| .mapperTemplate("templates/mapper.java").enableMapperAnnotation() |
| .controllerBuilder().enableFileOverride().enableRestStyle() |
| .build() |
| ) |
| .templateEngine(new FreemarkerTemplateEngine()) |
| .execute(); |
| } |
| } |
| |
| |
MybatisPlusConfig.java
| package com.booksSystem.config; |
| |
| import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; |
| import org.springframework.context.annotation.Bean; |
| import org.springframework.context.annotation.Configuration; |
| import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; |
| |
| |
| |
| |
| |
| |
| |
| @Configuration |
| public class MybatisPlusConfig { |
| |
| @Bean |
| public MybatisPlusInterceptor mybatisPlusInterceptor() { |
| MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); |
| |
| interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); |
| return interceptor; |
| } |
| } |
| |
SwaggerUiConfig.java
| package com.booksSystem.config; |
| |
| import io.swagger.v3.oas.annotations.ExternalDocumentation; |
| import io.swagger.v3.oas.annotations.OpenAPIDefinition; |
| import io.swagger.v3.oas.annotations.info.Contact; |
| import io.swagger.v3.oas.annotations.info.Info; |
| import io.swagger.v3.oas.annotations.info.License; |
| import io.swagger.v3.oas.annotations.servers.Server; |
| import org.springframework.boot.SpringBootConfiguration; |
| |
| |
| |
| |
| |
| @SpringBootConfiguration |
| @OpenAPIDefinition( |
| // ## API的基本信息,包括标题、版本号、描述、联系人等 |
| info = @Info( |
| title = "Swagger3.0 (Open API) 框架学习示例文档", // Api接口文档标题(必填) |
| description = "学习Swagger框架而用来定义测试的文档", // Api接口文档描述 |
| version = "1.2.1", // Api接口版本 |
| termsOfService = "https://example.com/", // Api接口的服务条款地址 |
| contact = @Contact( |
| name = "lambert", // 作者名称 |
| email = "***@163.com", // 作者邮箱 |
| url = "https://www.cnblogs.com/lambertlt/" // 介绍作者的URL地址 |
| ), |
| license = @License( // 设置联系人信息 |
| name = "Apache 2.0", // 授权名称 |
| url = "https://www.apache.org/licenses/LICENSE-2.0.html" // 授权信息 |
| ) |
| ), |
| // ## 表示服务器地址或者URL模板列表,多个服务地址随时切换(只不过是有多台IP有当前的服务API) |
| servers = { |
| @Server(url = "http://localhost:8080/", description = "本地测试服务器"), |
| // @Server(url = "http://192.168.2.236/demo/", description = "本地服务器二服务"), |
| }, |
| externalDocs = @ExternalDocumentation(description = "更多内容请查看该链接", url = "xxx")) |
| public class SwaggerUiConfig { |
| } |
| |
WebMvcConfig.java
| package com.booksSystem.config; |
| |
| import org.springframework.context.annotation.Configuration; |
| import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; |
| import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; |
| |
| @Configuration |
| public class WebMvcConfig implements WebMvcConfigurer { |
| |
| @Override |
| public void addResourceHandlers(ResourceHandlerRegistry registry) { |
| registry.addResourceHandler("/swagger-ui/**").addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/").resourceChain(false); |
| registry.addResourceHandler("/swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/"); |
| registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/"); |
| registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/"); |
| } |
| } |
| |
| |
controller.java.ftl
| package ${package.Controller}; |
| |
| import org.springframework.web.bind.annotation.RequestMapping; |
| import io.swagger.v3.oas.annotations.tags.Tag; |
| import java.util.List; |
| <#if restControllerStyle> |
| import org.springframework.web.bind.annotation.RestController; |
| <#else> |
| import org.springframework.stereotype.Controller; |
| </#if> |
| <#if superControllerClassPackage??> |
| import ${superControllerClassPackage}; |
| </#if> |
| |
| |
| |
| |
| |
| |
| |
| <#if restControllerStyle> |
| @RestController |
| <#else> |
| @Controller |
| </#if> |
| @Tag( name = "${table.controllerName}", description = "${table.comment}") |
| @RequestMapping("<#if package.ModuleName?? && package.ModuleName != "">/${package.ModuleName}</#if>/<#if controllerMappingHyphenStyle>${controllerMappingHyphen}<#else>${table.entityPath}</#if>") |
| <#if kotlin> |
| class ${table.controllerName}<#if superControllerClass??> : ${superControllerClass}()</#if> |
| <#else> |
| <#if superControllerClass??> |
| public class ${table.controllerName} extends ${superControllerClass} { |
| <#else> |
| public class ${table.controllerName} { |
| </#if> |
| |
| @Autowired |
| private ${table.serviceName} ${table.serviceName ? substring(1) ? uncap_first}; |
| |
| @GetMapping(value = "") |
| public ResponseEntity<List> all() { |
| List<${entity}> list = ${table.serviceName ? substring(1) ? uncap_first}.list(); |
| return new ResponseEntity<>(list, HttpStatus.OK); |
| } |
| |
| } |
| </#if> |
| |
service.java.ftl
| package ${package.Service}; |
| |
| import ${package.Entity}.${entity}; |
| import ${superServiceClassPackage}; |
| |
| |
| |
| |
| |
| |
| |
| <#if kotlin> |
| interface ${table.serviceName} : ${superServiceClass}<${entity}> |
| <#else> |
| public interface ${table.serviceName} extends ${superServiceClass}<${entity}> { |
| |
| } |
| </#if> |
| |
| |
serviceImpl.java.ftl
| package ${package.ServiceImpl}; |
| |
| import ${package.Entity}.${entity}; |
| import ${package.Mapper}.${table.mapperName}; |
| <#if generateService> |
| import ${package.Service}.${table.serviceName}; |
| </#if> |
| import ${superServiceImplClassPackage}; |
| import org.springframework.stereotype.Service; |
| |
| |
| |
| |
| |
| |
| @Service |
| <#if kotlin> |
| open class ${table.serviceImplName} : ${superServiceImplClass}<${table.mapperName}, ${entity}>()<#if generateService>, ${table.serviceName}</#if> { |
| |
| } |
| <#else> |
| public class ${table.serviceImplName} extends ${superServiceImplClass}<${table.mapperName}, ${entity}><#if generateService> implements ${table.serviceName}</#if> { |
| @Autowired |
| ${entity}Mapper ${entity ? uncap_first}Mapper; |
| } |
| </#if> |
| |
| |
entity.java.ftl
| package ${package.Entity}; |
| |
| <#list table.importPackages as pkg> |
| import ${pkg}; |
| </#list> |
| <#if springdoc> |
| import io.swagger.v3.oas.annotations.media.Schema; |
| <#elseif swagger> |
| import io.swagger.v3.oas.annotations.media.Schema; |
| </#if> |
| <#if entityLombokModel> |
| import lombok.Data; |
| <#if chainModel> |
| import lombok.experimental.Accessors; |
| </#if> |
| import java.util.Date; |
| </#if> |
| |
| |
| |
| |
| |
| |
| |
| <#if entityLombokModel> |
| @Data |
| <#if chainModel> |
| @Accessors(chain = true) |
| </#if> |
| </#if> |
| <#if table.convert> |
| @TableName("${schemaName}${table.name}") |
| </#if> |
| <#if springdoc> |
| @Schema(name = "${entity}", description = "${table.comment!}") |
| <#elseif swagger> |
| @Schema( name = "${entity}对象 - ${table.comment!}") |
| </#if> |
| <#if superEntityClass??> |
| public class ${entity} extends ${superEntityClass}<#if activeRecord><${entity}></#if> { |
| <#elseif activeRecord> |
| public class ${entity} extends Model<${entity}> { |
| <#elseif entitySerialVersionUID> |
| public class ${entity} implements Serializable { |
| <#else> |
| public class ${entity} { |
| </#if> |
| <#if entitySerialVersionUID> |
| |
| private static final long serialVersionUID = 1L; |
| </#if> |
| <#-- ---------- BEGIN 字段循环遍历 ----------> |
| <#list table.fields as field> |
| <#if field.keyFlag> |
| <#assign keyPropertyName="${field.propertyName}"/> |
| </#if> |
| |
| <#if field.comment!?length gt 0> |
| <#if springdoc> |
| @Schema(description = "${field.comment}") |
| <#elseif swagger> |
| @Schema( name = "${field.comment}" ) |
| <#else> |
| |
| |
| |
| </#if> |
| </#if> |
| <#if field.keyFlag> |
| <#-- 主键 --> |
| <#if field.keyIdentityFlag> |
| @TableId(value = "${field.annotationColumnName}", type = IdType.AUTO) |
| <#elseif idType??> |
| @TableId(value = "${field.annotationColumnName}", type = IdType.${idType}) |
| <#elseif field.convert> |
| <#-- @TableId("${field.annotationColumnName}")--> |
| @TableId(value = "${field.annotationColumnName}", type = IdType.AUTO) |
| </#if> |
| <#-- 普通字段 --> |
| <#elseif field.fill??> |
| <#-- ----- 存在字段填充设置 -----> |
| <#if field.convert> |
| @TableField(value = "${field.annotationColumnName}", fill = FieldFill.${field.fill}) |
| <#else> |
| @TableField(fill = FieldFill.${field.fill}) |
| </#if> |
| <#elseif field.convert> |
| @TableField("${field.annotationColumnName}") |
| </#if> |
| <#-- 乐观锁注解 --> |
| <#if field.versionField> |
| @Version |
| </#if> |
| <#-- 逻辑删除注解 --> |
| <#if field.logicDeleteField> |
| @TableLogic |
| </#if> |
| <#if field.propertyType=="LocalDateTime"> |
| @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") |
| private Date ${field.propertyName}; |
| <#else> |
| private ${field.propertyType} ${field.propertyName}; |
| </#if> |
| </#list> |
| |
| <#------------ END 字段循环遍历 ----------> |
| <#if !entityLombokModel> |
| <#list table.fields as field> |
| <#if field.propertyType == "boolean"> |
| <#assign getprefix="is"/> |
| <#else> |
| <#assign getprefix="get"/> |
| </#if> |
| |
| public ${field.propertyType} ${getprefix}${field.capitalName}() { |
| return ${field.propertyName}; |
| } |
| |
| <#if chainModel> |
| public ${entity} set${field.capitalName}(${field.propertyType} ${field.propertyName}) { |
| <#else> |
| public void set${field.capitalName}(${field.propertyType} ${field.propertyName}) { |
| </#if> |
| this.${field.propertyName} = ${field.propertyName}; |
| <#if chainModel> |
| return this; |
| </#if> |
| } |
| </#list> |
| </#if> |
| <#if entityColumnConstant> |
| <#list table.fields as field> |
| |
| public static final String ${field.name?upper_case} = "${field.name}"; |
| </#list> |
| </#if> |
| <#if activeRecord> |
| |
| @Override |
| public Serializable pkVal() { |
| <#if keyPropertyName??> |
| return this.${keyPropertyName}; |
| <#else> |
| return null; |
| </#if> |
| } |
| </#if> |
| <#if !entityLombokModel> |
| |
| @Override |
| public String toString() { |
| return "${entity}{" + |
| <#list table.fields as field> |
| <#if field_index==0> |
| "${field.propertyName} = " + ${field.propertyName} + |
| <#else> |
| ", ${field.propertyName} = " + ${field.propertyName} + |
| </#if> |
| </#list> |
| "}"; |
| } |
| </#if> |
| } |
| |
| |
| |
mapper.java.ftl
| package ${package.Mapper}; |
| |
| import ${package.Entity}.${entity}; |
| import ${superMapperClassPackage}; |
| <#if mapperAnnotationClass??> |
| import ${mapperAnnotationClass.name}; |
| </#if> |
| |
| |
| |
| |
| |
| |
| |
| <#if mapperAnnotationClass??> |
| @${mapperAnnotationClass.simpleName} |
| </#if> |
| <#if kotlin> |
| interface ${table.mapperName} : ${superMapperClass}<${entity}> |
| <#else> |
| public interface ${table.mapperName} extends ${superMapperClass}<${entity}> { |
| |
| } |
| </#if> |
| |
| |
mapper.xml.java.ftl
| <?xml version="1.0" encoding="UTF-8"?> |
| <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
| <mapper namespace="${package.Mapper}.${table.mapperName}"> |
| |
| <#if enableCache> |
| <!-- 开启二级缓存 --> |
| <cache type="${cacheClassName}"/> |
| |
| </#if> |
| <#if baseResultMap> |
| <!-- 通用查询映射结果 --> |
| <resultMap id="BaseResultMap" type="${package.Entity}.${entity}"> |
| <#list table.fields as field> |
| <#if field.keyFlag><#--生成主键排在第一位--> |
| <id column="${field.name}" property="${field.propertyName}" /> |
| </#if> |
| </#list> |
| <#list table.commonFields as field><#--生成公共字段 --> |
| <result column="${field.name}" property="${field.propertyName}" /> |
| </#list> |
| <#list table.fields as field> |
| <#if !field.keyFlag><#--生成普通字段 --> |
| <result column="${field.name}" property="${field.propertyName}" /> |
| </#if> |
| </#list> |
| </resultMap> |
| |
| </#if> |
| <#if baseColumnList> |
| <!-- 通用查询结果列 --> |
| <sql id="Base_Column_List"> |
| <#list table.commonFields as field> |
| ${field.columnName}, |
| </#list> |
| ${table.fieldNames} |
| </sql> |
| |
| </#if> |
| </mapper> |
| |
| |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现