2. 【threeking】创建服务,并选择引入常用组建

2. 创建服务

1. 创建项目

引用各种需要的包

dependencyManagement包引用管理,可以在父工程中配置,也可以在子工程中配置

下面这四个都用就完了:

  • spring-cloud-alibaba-dependencies

    alibaba出的springcloud依赖管理,要使用alibaba的全家桶这个肯定少不了

  • spring-cloud-dependencies

    springcloud官方的依赖,微服务架构下肯定都得用

  • spring-boot-dependencies

    springboot的官方依赖,我们项目就是基于springboot开发,这个肯定少不了

  • lombok

    简约化编程你离不了它,反正大家都在用

<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.2.3.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR8</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.3.4.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.12</version>
                <optional>true</optional>
            </dependency>
        </dependencies>

    </dependencyManagement>
2. 使用mysql

2.1 mysql链接驱动

<!--mysql 链接驱动-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 采用最简单的JDBC链接-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- 数据库配置-->
spring:  
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/tk_user?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: 123456
@RestController
public class IndexController {
    @Autowired
    JdbcTemplate jdbcTemplate;
    
    @GetMapping("/jdbc")
    public Object jdbcStart(){
        return jdbcTemplate.queryForList("select * from user_info");
    }
}
# 查询结果
[
    {
        "id": 1,
        "name": "张三",
        "sex": 1,
        "create_time": "2020-10-19T16:18:48.000+00:00",
        "cretae_user": "sys",
        "update_time": "2020-10-19T16:19:05.000+00:00",
        "update_user": "sys",
        "data_status": 1
    }
]

2.2 采用阿里的druid链接池

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.10</version>
 </dependency>
<!-- 数据库配置-->
spring:  
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/tk_user?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: 123456
    type: com.alibaba.druid.pool.DruidDataSource

打开http://localhost:{port}/driud,可以看到数据库使用情况

durid数据库连接池,主要是做连接池最大化限制

3. 使用mybaits-plus实现数据库访问

为什么选择的是mybaits-plus,为什么不选择mybaits,MyBatis-Plus (简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

3.1 代码生成器

参考官网写代码生成

public class MybaitsGenerator {

    static String projectPath = System.getProperty("user.dir");
    /**
     * 设置全局配置
     * @return
     */
   static GlobalConfig initGlobalConfig() {

       GlobalConfig gc = new GlobalConfig();
       //当前项目路径

       gc.setOutputDir(projectPath + "/threeking-user/src/main/java");
       gc.setAuthor("ah");
       gc.setOpen(false);
       gc.setSwagger2(true);
       //是否覆盖 一般选择false
       gc.setFileOverride(false);

       gc.setDateType(DateType.ONLY_DATE);


       return gc;
   }

    /***
     * 设置数据源
     * @return
     */
    static DataSourceConfig initDataSourceConfig(){
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setDriverName("com.mysql.jdbc.Driver");
        dsc.setUrl("jdbc:mysql://127.0.0.1:3306/tk_user?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC");
        dsc.setUsername("root");
        dsc.setPassword("123456");

        return dsc;
    }

    /**
     * 包配置
     * @return
     */
    static PackageConfig iniPackageConfig(){
        PackageConfig pc = new PackageConfig();
        pc.setParent("com.threeking.service");
        pc.setModuleName("user");
        //controller设置为空,则不生成controller
        //pc.setController("");
        pc.setEntity("entity");
        return pc;
    }

    /**
     * 配置生成策略
     * @return
     */
    static StrategyConfig initStrategyConfig(){
        StrategyConfig sc = new StrategyConfig();
        sc.setInclude("user_info");
        sc.setNaming(NamingStrategy.underline_to_camel);
        sc.setColumnNaming(NamingStrategy.underline_to_camel);
        sc.setEntityLombokModel(true);
        //sc.setEntityBuilderModel(true);
        sc.setChainModel(true);

        //逻辑删除
        /**
         * 修改1 为有效,0为无效
         * mybatis-plus:
         *   global-config:
         *     db-config:
         *       logic-delete-value: 0 #逻辑已删除值(默认为1)
         *       logic-not-delete-value: 1 #逻辑已删除值(默认为0)
         */
        //sc.setLogicDeleteFieldName("data_status");
        sc.setRestControllerStyle(false);
        return sc;
    }

    /**
     * 自定义配置
     * @return
     */
    static InjectionConfig initInjectionConfig(String moduleName){
        InjectionConfig cfg = new InjectionConfig(){
            @Override
            public void initMap() {
                // to do nothing
            }
        };
//        // 如果模板引擎是 freemarker
//        String templatePath = "/templates/mapper.xml.ftl";
//        // 如果模板引擎是 velocity
//        // String templatePath = "/templates/mapper.xml.vm";
//        // 自定义输出配置
//        ArrayList<FileOutConfig> foclist = new ArrayList<>();
//
//        foclist.add(new FileOutConfig() {
//            @Override
//            public String outputFile(TableInfo tableInfo) {
//                // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
//                return projectPath + "/src/main/resources/mapper/" + moduleName
//                        + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
//            }
//        });


        cfg.setFileCreate(new IFileCreate() {
            @Override
            public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) {

                // 如果是Entity类型,直接放过输出文件
                if(fileType == FileType.ENTITY){
                    return true;
                }
                // 判断自定义文件夹是否需要创建
//                checkDir("调用默认方法创建的目录,自定义目录用");
//                if (fileType == FileType.MAPPER) {
//                    // 已经生成 mapper 文件判断存在,不想重新生成返回 false
//                    return !new File(filePath).exists();
//                }
                boolean exist = new File(filePath).exists();

                //文件不存在或者全局配置的fileOverride为true才写文件
                return !exist || configBuilder.getGlobalConfig().isFileOverride();

            }
        });


        return cfg;
    }

    /**
     * 模板配置
     * @return
     */
    static TemplateConfig initTemplateConfig(){
        TemplateConfig templateConfig = new TemplateConfig();
        templateConfig.setController("");
        return templateConfig;
    }

    public static void main(String[] args) {
        //代码生成器
        AutoGenerator mpg = new AutoGenerator();

        //配置策略
        //1. 全局配置

        mpg.setGlobalConfig(initGlobalConfig());

        //2. 数据源
        mpg.setDataSource(initDataSourceConfig());
        //3. 包配置
        PackageConfig pc = iniPackageConfig();
        mpg.setPackageInfo(pc);

        //4. 自定义配置 还没弄明白
        mpg.setCfg(initInjectionConfig(pc.getModuleName()));
        //5. 配置模板
        mpg.setTemplate(initTemplateConfig());
        mpg.setTemplateEngine(new FreemarkerTemplateEngine());

        //6. 策略配置
        mpg.setStrategy(initStrategyConfig());


        //执行
        mpg.execute();
    }
}

3.2 日志

这样,在控制台就可以输出SQL记录

#配置日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

3.3 MybitasPulsConfig

这个配置可以做很多事情,第一个事情,就是可以把**@MapperScan** 从项目的Application启动项拿走

@Configuration
@MapperScan("com.threeking.service.user.mapper")
public class MybatisPlusConfig {
}
4. 使用swagger2
4.1 旧时代额swagger

选择适用swagger做RESTful api文档接口

 <dependency>
     <groupId>io.springfox</groupId>
     <artifactId>springfox-swagger2</artifactId>
     <version>2.6.1</version>
</dependency>
<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>swagger-bootstrap-ui</artifactId>
    <version>1.9.6</version>
</dependency>

使用swagger-bootstrao-ui做交互界面

@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.threeking.service.user"))
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("swagger-bootstrap-ui RESTful APIs")
                .description("swagger-bootstrap-ui")
                .termsOfServiceUrl("http://localhost:6601/")
                .contact(new Contact("ah","doc.html",""))
                .version("1.0")
                .build();
    }
}
4.2 新时代的swagger,主要是swagger3.0之后的
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-boot-starter</artifactId>
    <version>3.0.0</version>
<dependency>
@EnableOpenApi
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

访问:http://localhost:8080/swagger-ui/index.html 即可,方便简洁

如果想要继续使用swagger-bootstrap-ui,在pom文件中添加

swagger-bootstrap-ui已经升级为knife4j,所以该方案废弃

<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>swagger-bootstrap-ui</artifactId>
    <version>1.9.6</version>
</dependency>

访问:http://localhost:8080/doc.html 即可

4.3 使用knife4j做ui界面

knife4j官方文档
在pom.xml文件中添加引用,

<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
    <version>3.0</version>
</dependency>

其他不用变即可

5.处理Api响应返回值

spring官方给的是ResponseEntity,可以满足各种请求

但是实际项目开发中,底层服务,并不需要这么多处理,简化处理,我将其包装成一个基础类

APIBaseResponse.java

@Getter
@Setter
public class APIBaseResponse implements Serializable {

	/**
	 * serialVersionUID
	 */
	private static final long serialVersionUID = 1L;

	public String getCode() {
		return code;
	}

	public void setCode(String code) {
		this.code = code;
	}

	public String getMsg() {
		return msg;
	}

	public void setMsg(String msg) {
		this.msg = msg;
	}

	/**
	 * 返回码
	 */
	private String code;

	/**
	 * 返回信息
	 */
	private String msg;


}

一个继承类APIResponse.java,适配各种返回值

public class APIResponse<T> extends APIBaseResponse {

    private static final long serialVersionUID = 1L;


    private T content;

    public T getContent() {
        return content;
    }

    public void setContent(T content) {
        this.content = content;
    }

    /**
     * 请求成功返回的结构数据
     * @param t t
     * @return ResponseContentOne
     */
    public static <T> APIResponse<T> successResp(T t){
        APIResponse<T> resp = new APIResponse<T>();
        resp.setCode("0");
        resp.setMsg("success");
        resp.setContent(t);
        return resp;
    }

    public static <T> APIResponse<T> successResp(){
        APIResponse<T> resp = new APIResponse<T>();
        resp.setCode("0");
        resp.setMsg("success");
        return resp;
    }

    /**
     * 请求失败返回的数据
     * @param msg msg
     * @return ResponseContentOne
     */
    public static <T> APIResponse<T> errorResp(String msg){
        return errorResp("1", msg);
    }

    /**
     * 请求失败返回的数据
     * @param msg msg
     * @return ResponseContentOne
     */
    public static <T> APIResponse<T> errorResp(String code, String msg){
        APIResponse<T> resp = new APIResponse<>();
        resp.setCode(code);
        resp.setMsg(msg);
        return resp;
    }

    public static <T> APIResponse<T> errorResp(BindingResult result){

        APIResponse<T> resp = new APIResponse<T>();
        resp.setCode("1");
        StringBuilder sb = new StringBuilder("");
        result.getAllErrors().stream()
                .forEach(err -> {
                    sb.append(((FieldError) err).getField())
                            .append(":")
                            .append(err.getDefaultMessage())
                            .append(" | ");
                });
        String errMsg = sb.toString().trim();
        errMsg = errMsg.substring(0, errMsg.lastIndexOf("|"));
        resp.setMsg(errMsg);
        return resp;
    }


    public static <T> APIResponse<T>  errorRes(BindingResult bindingResult) {
        APIResponse<T> resp = new APIResponse<>();
        List<ObjectError> ls=bindingResult.getAllErrors();
        resp.setCode("1");
        resp.setMsg(ls.get(0).getDefaultMessage());
        return resp;
    }
}

使用方式

    //使用方式
    @PostMapping("/test")
    public APIResponse test(){

		return APIResponse.successResp();
        return APIResponse.successResp("执行成功");
        return APIResponse.errorResp("失败");
        return APIResponse.errorResp("2","自定义code格式失败");
    }

在项目过程中会有大量是使用,当然GitHub上还有其他大佬们的实现方式,按照自己的业务需求定制就可以了

6.入参校验

每个服务对外提供的都是一个个的API接口,那么每个接口的入参都需要特定的校验,如下:

@Getter
@Setter
public class PhoneDto {   
    private String phone;
    private String verify;
}

这是一个手机验证码入参实体,我们需要在接受它的方法上对每个参数进行校验做处理, 参数校验有很多种方式

1. 原始方案,没一个做判断,做返回处理
   @PostMapping("/phoneRegister")
    public String testPhoneRegister(@RequestBody PhoneDto dto){
        //参数判断
        if(!checkparam(dto)){return "参数不对";}
        // 其他处理
        return "phoneRegister...";
    }

这种方案,当然现在已经没有人选用

2. 借助javax.validation 实现,我们修改实体类
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;

@Getter
@Setter
public class PhoneDto {

    @Pattern(regexp = "1[\\d]{10}",message = "请输正确手机号")
    private String phone;
    @NotEmpty(message = "验证码不能为空")
    private String verify;
}

修改接口,增加@Valid注解

@PostMapping("/phoneRegister")
public String testPhoneRegister(@RequestBody @Valid PhoneDto dto){
   
    // 其他处理

    return "phoneRegister...";
}

请求后,接口会报这样的错误

在这里插入图片描述

虽然拦住了,但是并不是我们想要的结果,前端肯定不喜欢这样返回

3.使用BindingResult方式返回错误信息
@PostMapping("/testPhoneRegister")
public String testPhoneRegister(@RequestBody @Valid PhoneDto dto, BindingResult result){

    if (result.hasErrors())
    {
        List<ObjectError> ls=result.getAllErrors();
        ls.forEach(e-> System.out.println(e.getDefaultMessage()));
        //默认返回第一个错误,大家可以根据需求定制
        return ls.get(0).getDefaultMessage();
    }
    return "phoneRegister...";
}

前端响应,接口回去我们的错误信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RDCikbgn-1604633189686)(C:\Users\wanghui\AppData\Roaming\Typora\typora-user-images\image-20201105143114785.png)]

此时,已经基本满足我们的需求

但是,我们这么写,就太繁琐了,每个接口都要写一套,这时候我们应该想到Spring Boot AOP编程了

4.使用RestControllerAdvice或者说ControllerAdvice实现参数校验

spring framework web controller advice

@RestControllerAdvice
public class ParamValidControllerAdvice {

    /**
     * 拦截MethodArgumentNotValidException类型的错误
     * 也就是我们定义Valid错误
     * 也可以做一些其他的错误拦截器
     * @param ex
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public APIResponse bindExceptionHandler(MethodArgumentNotValidException ex){
        return APIResponse.errorResp(ex.getBindingResult());
    }
}

运行结果

前端拿到这样的结果,处理起来就比较方便了

7.统一全局异常类配置

前面校验引发的,我们后台异常怎么处理,一般情况下,都是写业务的时候,各种try-catch来处理我们的异常

但在实际开发中,我们并不想每个都处理,所以一直把异常throw出去,那么最外层用捕获的话,就回满屏幕都是try-catch

    @PostMapping("/test1")
    public void test1() {
        try {
            //to do something
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @PostMapping("/test2")
    public void test2() {
        try {
            //to do something
        } catch (Exception e) {
            e.printStackTrace();
        }

对我们开发者来说,我们只想考虑业务怎么写,所以我们一般都使用上面讲到的RestControllerAdvice来做统一的错误日志处理

/**
 * 全局异常处理类
 */
@Slf4j
@RestControllerAdvice
public class GlobalControllerAdvice {

    /**
     * 拦截MethodArgumentNotValidException类型的错误
     * 也就是我们定义Valid错误
     * 也可以做一些其他的错误拦截器
     * @param ex
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public APIResponse<String> validExceptionHandler(MethodArgumentNotValidException ex){
        return APIResponse.errorResp(ex.getBindingResult());
    }

    /**
     * 处理运行异常
     */
    @ExceptionHandler(RuntimeException.class)
    public APIResponse<String> runtimeExceptionHandler(HttpServletRequest request, RuntimeException ex) {
        log.error("", ex);
        log.error("请求地址:" + request.getRequestURL());
        log.error("请求参数: " + JSONUtils.toJSONString(request.getParameterMap()));
        return APIResponse.errorResp(ex.getMessage());
    }

    /**
     * 用来捕获404,400这种无法到达controller的错误
     * @param ex
     * @return
     */
    @ExceptionHandler(Exception.class)
    public APIResponse<String> exceptionHandler(Exception ex){
        log.error("", ex);
        if (ex instanceof NoHandlerFoundException) {
            return APIResponse.errorResp("404",ex.getMessage());
        } else {
            return APIResponse.errorResp("500",ex.getMessage());
        }
    }
}

这个我们修改代码

@PostMapping("/test1")
public void test1() throws Exception{
    //to do something
}
@PostMapping("/test2")
public void test2() throws Exception{
    //to do something
}

这样是不是清爽很多了

8.使用Redis

缓存选择使用Redis,在pom文件中添加引用

 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  redis:
    host: 127.0.0.1
    port: 6379
    database: 6
    jedis:
      pool:
        max-active: 20
        max-idle: 100
        min-idle: 1
        max-wait: 1000ms

增加一个redis配置类

@Configuration
public class RedisTemplateConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();

        redisTemplate.setConnectionFactory(redisConnectionFactory);
        // 使用Jackson2JsonRedisSerialize 替换默认序列化
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
                ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);

        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        // 设置value的序列化规则和 key的序列化规则
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();

        return redisTemplate;
    }

}

测试

    @Autowired
    RedisTemplate<String, Object> redisTemplate;

    @Autowired
    StringRedisTemplate stringRedisTemplate;

  public void test(){
 	stringRedisTemplate.opsForValue().set("1332333333",
                "123",
                200,
                TimeUnit.SECONDS);
      
      redisTemplate.opsForValue().set("1332333333",
                "123",
                200,
                TimeUnit.SECONDS);
  }

转移到下一章:3.构建微服务体系

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值