分布式读书

Kotlin +SpringBoot + MyBatis完美搭建

2019-03-19  本文已影响92人  爱学习的蹭蹭

一、为什么学习(初衷):

二、技术选型

Kotlin框架.png

三、kotlin背景简要描述

四、Spring Boot 发展路线简要描述

4.1 SpringBoot插件使用

五、Kotlin插件

六、jsoup简要

七、fastJson

八、HttpClient

九、Maven

十、MyBatis-Plus

十一、工程准备

十二、工程结构

image.png
image.png
image.png

十三、工程代码结构

@EnableAsync
@Configuration
@EnableScheduling
@EnableAutoConfiguration  //启用读取配置
@ComponentScan("com.flong.kotlin")  //扫描com.flong文件目录下
@SpringBootApplication(scanBasePackages = ["com.flong.kotlin"] )
//@SpringBootApplication(scanBasePackages = arrayOf("com.flong.kotlin")) 这种写法也OK
open class Application {
   @Bean
   open fun jspViewResolver(): InternalResourceViewResolver {
       var resolver = InternalResourceViewResolver();
       resolver.setPrefix("/WEB-INF/views/");
       resolver.setSuffix(".jsp");
       return resolver;
   }
   //静态类
   companion object {

       /**启动SpringBoot的主入口.*/
       @JvmStatic fun main(args: Array<String>) {
           //*args的星号表示引用相同类型的变量
           SpringApplication.run(Application::class.java, *args)
       }
   }
}

@Configuration
@ConditionalOnClass(WebMvcConfigurer::class)
@Order(Ordered.HIGHEST_PRECEDENCE)
open class WebConfig : WebMvcConfigurer{

    constructor() : super()

    @Bean
    open fun customConverters(): HttpMessageConverters {
        //创建fastJson消息转换器
        var fastJsonConverter = FastJsonHttpMessageConverter()
        //创建配置类
        var fastJsonConfig = FastJsonConfig()
        //修改配置返回内容的过滤
        fastJsonConfig.setSerializerFeatures(
                // 格式化
                SerializerFeature.PrettyFormat,
                // 可解决long精度丢失 但会有带来相应的中文问题
                //SerializerFeature.BrowserCompatible,
                // 消除对同一对象循环引用的问题,默认为false(如果不配置有可能会进入死循环)
                SerializerFeature.DisableCircularReferenceDetect,
                // 是否输出值为null的字段,默认为false
                SerializerFeature.WriteMapNullValue,
                // 字符类型字段如果为null,输出为"",而非null
                SerializerFeature.WriteNullStringAsEmpty,
                // List字段如果为null,输出为[],而非null
                SerializerFeature.WriteNullListAsEmpty
        )
        // 日期格式
        fastJsonConfig.dateFormat = "yyyy-MM-dd HH:mm:ss"
        
        // long精度问题
        var serializeConfig = SerializeConfig.globalInstance
        serializeConfig.put(Integer::class.java, ToStringSerializer.instance)
        serializeConfig.put(BigInteger::class.java, ToStringSerializer.instance)
        serializeConfig.put(Long::class.java, ToStringSerializer.instance)
        serializeConfig.put(Long::class.javaObjectType, ToStringSerializer.instance)
        fastJsonConfig.setSerializeConfig(serializeConfig)
        
        //处理中文乱码问题
        var fastMediaTypes = ArrayList<MediaType>()
        fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8)
        fastMediaTypes.add(MediaType(MediaType.TEXT_HTML, Charset.forName("UTF-8")))
        fastMediaTypes.add(MediaType(MediaType.TEXT_PLAIN, Charset.forName("UTF-8")))
        fastMediaTypes.add(MediaType(MediaType.APPLICATION_FORM_URLENCODED, Charset.forName("UTF-8")))
        fastMediaTypes.add(MediaType.MULTIPART_FORM_DATA)
        
        fastJsonConverter.setSupportedMediaTypes(fastMediaTypes)
        fastJsonConverter.setFastJsonConfig(fastJsonConfig)
        //将fastjson添加到视图消息转换器列表内
        return HttpMessageConverters(fastJsonConverter)
    }

    /**
     * 拦截器
     */
    open override fun addInterceptors(registry: InterceptorRegistry) {
        //registry.addInterceptor(logInterceptor).addPathPatterns("/**")
        //registry.addInterceptor(apiInterceptor).addPathPatterns("/**")
    }

    /**
     * cors 跨域支持 可以用@CrossOrigin在controller上单独设置
     */
    open override fun addCorsMappings(registry: CorsRegistry) {
        registry.addMapping("/**")
                //设置允许跨域请求的域名
                .allowedOrigins("*")
                //设置允许的方法
                .allowedMethods("*")
                //设置允许的头信息
                .allowedHeaders("*")
                //是否允许证书 不再默认开启
                .allowCredentials(java.lang.Boolean.TRUE)
    }
}
open class BaseService<M : BaseMapper<T>, T, Q : Query> : ServiceImpl<M, T> {
    //构造方法
    constructor() : super()
    var IN_SIZE: Int = 1000;
    //新增
    fun add(obj: T): Boolean {
        var affCnt = baseMapper.insert(obj);
        return null != affCnt && affCnt > 0;
    }
       //更加id去更新数据
    override fun updateById(obj: T): Boolean {
        var affCnt = baseMapper.updateById(obj);
        return null != affCnt && affCnt > 0;
    }
    /**
     * 删除
     */
    override fun deleteById(id: Serializable): Boolean {
        var affCnt = baseMapper.deleteById(id);
        return null != affCnt && affCnt > 0;
    }
    /**
     * ID 取对象,取不到为空
     */
    fun get(id: Serializable): T {
        return baseMapper.selectById(id);
    }
    fun getMapper(): M {
        return this.baseMapper;
    }
    fun buildQuery(query: Q): EntityWrapper<T> {
        return EntityWrapper<T>();
    }
}
@Configuration
@MapperScan(basePackages = arrayOf(DataSourceConfig.PACKAGE), sqlSessionFactoryRef = "sessionFactory")
open class DataSourceConfig  {
    //静态常量
    companion object {
        //const 关键字用来修饰常量,且只能修饰  val,不能修饰var,  companion object 的名字可以省略,可以使用 Companion来指代
         const val  PACKAGE = "com.flong.kotlin.*.mapper";
         const val TYPEALIASESPACKAGE = "com.flong.kotlin.modules.entity";
    }

    final var MAPPERLOCATIONS = "classpath*:mapper/*.xml";
    @Primary
    @Bean("dataSource")
    @ConfigurationProperties(prefix = "spring.datasource")
    open fun dataSource(): DataSource {
        return DruidDataSource();
    }


    @Bean
    open fun transactionManager(@Qualifier("dataSource") dataSource: DataSource): DataSourceTransactionManager {
        return DataSourceTransactionManager(dataSource);
    }

    @Bean
    open fun sessionFactory(dataSource: DataSource):SqlSessionFactory {
        //===1.mybatis-plus globalConfig配置
        var cfg =  GlobalConfiguration();
        
        // 字段的驼峰下划线转换
        cfg.setDbColumnUnderline(true);
        // 全局主键策略
        cfg.setIdType(IdType.AUTO.key);
        // 全局逻辑删除
        cfg.sqlInjector         = LogicSqlInjector()
        cfg.logicDeleteValue    = "1"
        cfg.logicNotDeleteValue = "0"
    
        //===2.构造sessionFactory(mybatis-plus)
        var sf = MybatisSqlSessionFactoryBean();
        sf.setDataSource(dataSource);
        sf.setMapperLocations(PathMatchingResourcePatternResolver().getResources(MAPPERLOCATIONS));
        sf.setGlobalConfig(cfg);
        sf.setTypeAliasesPackage(TYPEALIASESPACKAGE) 
        // 分页插件
        sf.setPlugins(arrayOf(PaginationInterceptor()))
        //return sf.getObject();
        return sf.`object`
    }

    
    
    /**
     * @Description 初始化druid
     * @Author      liangjl
     * @Date        2018年1月17日 下午4:52:05
     * @return 参数
     * @return ServletRegistrationBean 返回类型 
     */
    @Bean
    open fun druidServlet() : ServletRegistrationBean<Servlet>{
        var bean:ServletRegistrationBean<Servlet> = ServletRegistrationBean(StatViewServlet(), "/druid/*") ;
        /** 初始化参数配置,initParams**/
        //登录查看信息的账号密码.
        bean.addInitParameter("loginUsername", "root");
        bean.addInitParameter("loginPassword", "root");
        //IP白名单 (没有配置或者为空,则允许所有访问)
        bean.addInitParameter("allow", "");
        //IP黑名单 (共存时,deny优先于allow) : 如果满足deny的话提示:Sorry, you are not permitted to view this page.
        bean.addInitParameter("deny", "192.88.88.88");
        //禁用HTML页面上的“Reset All”功能
        bean.addInitParameter("resetEnable", "false");
        return bean;
    }
    @Bean
    open fun filterRegistrationBean() : FilterRegistrationBean<Filter>{
        var filterRegistrationBean =   FilterRegistrationBean<Filter>()
        filterRegistrationBean.setFilter(WebStatFilter());
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*");
        return filterRegistrationBean;
    }

     
}
open class PageVO<E>  {
    /**
     * 总条数
     */
    var total: Long? = null
    /**
     * 查询结果
     */
    var records: Collection<E>? = null
    /**
     * 当前页
     */
    var page: Int? = null
    /**
     * 总页数
     */
    var totalPage: Long? = null
    /**
     * 每页显示条数
     */
    var pageSize: Int? = null
    //init { }

    //多重构造方法
    constructor(records: Collection<E>?, pageVO: PageVO<E>){
        this.records = records;
        this.total = pageVO.total;
        this.totalPage = pageVO.totalPage;
        this.page = pageVO.page;
        this.pageSize = pageVO.pageSize;
        
    }
    //多重构造方法
    constructor(records: Collection<E>?, page: Page<Any>){
        this.records = records;
        this.total = page.getTotal();
        this.totalPage = page.getPages();
        this.page = page.getCurrent();
        this.pageSize = page.getSize();
        
    }
}
open class PageUtil {
  /**
   * 取mybatis-plus分页对象
   */
  open fun getPage(query : Query):Page<Any>? {
      return Page(query.page, query.pageSize);
  }
}
/**
 *data保持数据data class就是一个类中只包含一些数据字段,类似于vo,pojo,java bean。一般而言,
 *我们在Java中定义了这个数据类之后要重写一下toString,equals等方法。要生成get,set方法
 *https://www.cnblogs.com/liuliqianxiao/p/7152773.html
 *注意mybatis查询数据,然后封装实体的时候,构造方法这里有点儿坑,查询的字段必须与构造方法一直。
 */
@TableName("t_user")
data  class User constructor(
    @TableId(value = "user_id", type = IdType.ID_WORKER)
    var userId: Long?= null,//用户Id主键,IdWork生成
    var userName: String? = null,//用户名
    var passWord: String? = null,//密码
    @TableLogic
    @JSONField(name = "isDeleted")
    var isDeleted: Int? = null,//删除
    var createTime: Date? = null //创建时间,允许为空,让数据库自动生成即可
  ) :Serializable{
 
    //手动重写toString方法
    override fun toString(): String {
        return "[User(userId = $userId,userName = $userName, passWord=$passWord,isDeleted=$isDeleted,createTime=$createTime),]"
    }

    //equals
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false
        other as User
        if (userId != other.userId) return false
        if (userName != other.userName) return false
        if (passWord != other.passWord) return false
        if (isDeleted != other.isDeleted) return false
        if (createTime != other.createTime) return false
        return true
    }
    override fun hashCode(): Int {
        var result = userId?.hashCode() ?: 0
        result = 31 * result + (userName?.hashCode() ?: 0)
        result = 31 * result + (passWord?.hashCode() ?: 0)
        result = 31 * result + (isDeleted?.hashCode() ?: 0)
        result = 31 * result + (createTime?.hashCode() ?: 0)
        
        return result
    }

}
interface IUserMapper : BaseMapper<User>{

    //这里的?表示当前是否对象可以为空 @see http://blog.csdn.net/android_gjw/article/details/78436707
    
    fun getUserList(query : UserQuery , page : Page<Any>): List<UserRespVo>?
 
}
<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 
<mapper namespace="com.flong.kotlin.modules.mapper.IUserMapper">
    
    <!-- 抽取公共字段 -->
    <sql id="find_AS_R">
        <![CDATA[
            SELECT A.user_Id as userId, A.USER_NAME as userName, 
            A.PASS_WORD as passWord,is_deleted as isDeleted,
            A.create_time as createTime
        ]]>
    </sql>
    
    
    <select id="getUserList" resultType="com.flong.kotlin.modules.vo.resp.UserRespVo" >
        <include refid="find_AS_R" /> FROM T_USER A 
        <where>
            <if test="userId != null">  and A.user_Id = #{userId}</if>
            <if test="userName != null and  userName !='' ">and A.USER_NAME = #{userName}</if>
        </where>
        
    </select>
   
   
</mapper>
@Service
open class UserService : BaseService<IUserMapper,User,UserQuery>() {
    //Kotlin lateinit 和 by lazy 的区别
    //http://blog.csdn.net/Sherlbon/article/details/72769843
    @Autowired lateinit var userMapper: IUserMapper;
    //根据
    open fun queryAllUser(): List<User>? {
        var wrapper = createWrapper();
        return this.selectList(wrapper);
    }
    open fun listPage(query : UserQuery) : PageVO<UserRespVo> ? {
        var page = PageUtil().getPage(query)  ;// 设置分页
        var dataList = userMapper.getUserList(query, page!!);//page!!强制告诉编辑器不可能为空
        
        var json = JSON.toJSONString(dataList);
        println(json)
        return  PageVO(dataList, page);// 获取分页数和总条数
    }
        
       //通过主键id进行查询
    open fun getUserId(userId: Long): User? {
        return get(userId);
    }

    //插入用户
    open fun addUser() {
        var userId = IdWorker.getId();
        var u = User(userId, "liangjl", "123456",null, Date());
        var json = JSON.toJSONString(u);
        println(json)
        add(u);
    }
    fun createWrapper(): Wrapper<User> {
        var wrapper = EntityWrapper<User>();
        wrapper.setEntity(User());//设置实体
        return wrapper;
    }

}

@RestController
@RequestMapping("/rest")
open class UserController : BaseController(){
    
    @Autowired private lateinit var userService: UserService
 
    @RequestMapping("/list1")
    fun list1():  String{
        return "NewFile" //跳转页面
    }
    
    //添加
    @RequestMapping("/add")
    fun add():  Unit{
        userService.addUser()
    }
    //删除
    @RequestMapping("/deletedById")
    fun deletedById(userId : Long):  Unit{
        userService.deleteById(userId);
    }

    //更新
    @RequestMapping("/update")
    fun update(user : User):  Unit{
        userService.updateById(user)
    }
    
    //根据Id查询用户
    @RequestMapping("/getUserId")
    fun getUserId(userId :Long):Any?{
        var user = userService.getUserId(userId);
        if(user ==null){
            var msgCode = UserMsgCode.FIND_NOT_USER;
            throw BaseException(msgCode.code!! ,msgCode.message!!)
        }
        return userService.getUserId(userId)
    }
    
    //查询所有
    @RequestMapping("/queryAllUser")
    fun queryAllUser():List<User>?{
        return userService.queryAllUser()
    }
    
    //分页查询
    @RequestMapping("listPage")
    fun listPage(query :UserQuery) :PageVO<UserRespVo> ? {
        var listPage = userService.listPage(query);
        return listPage;
    }
    
    @RequestMapping("/getBody")
    fun getBody(@RequestBody jsonText:UserRespVo){
        println(jsonText.component2())
        println(jsonText.userName)
    }
    
    
}

*数据库配置

logging.evel.root=info
logging.evel.com.flong.kotlin=debug

# ====================mybatis_config====================
    
spring.profiles.active=dev
#====================datasource====================
#spring.datasource.name=na
spring.datasource.url=jdbc:mysql://localhost:3306/kotlin?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=20
spring.datasource.maxWait=60000
spring.datasource.timeBetweenEvictionRunsMillis=60000
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=SELECT 1 FROM DUAL
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
spring.datasource.poolPreparedStatements=true
spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
#spring.datasource.filters=stat,wall,log4j,slf4j
spring.datasource.connectionInitSqls=set names utf8mb4
spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000

spring.devtools.restart.enabled=false


spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp
 

package com.flong.kotlin.utils

/**
 * object表示这个类是单例模式
 * @Description HttpClientUtil帮助类
 * @Author      liangjilong
 * @Date        2018年12月17日
 */
 object HttpClientUtil {

    
    private val log: Logger = LoggerFactory.getLogger(HttpClientUtil::class.java)
    
    private var JSON_APPLICATION        = "application/json; charset=utf-8"
    private var CONNECT_TIMEOUT         = 1000 * 20 
    private var MAX_TIMEOUT             = 1000 * 20
    private var DEFAULT_ENCODING        = "UTF-8"
    private var LINE                    = System.getProperty("line.separator")//换行相当于\n
    
    private val requestConfig : RequestConfig
    init {
        // 设置连接池  
        var connMgr = PoolingHttpClientConnectionManager()
        // 设置连接池大小  
        connMgr.maxTotal = 100
        connMgr.setDefaultMaxPerRoute(connMgr.maxTotal)

        var configBuilder = RequestConfig.custom()
        // 设置连接超时  
        configBuilder.setConnectTimeout(MAX_TIMEOUT)
        // 设置读取超时  
        configBuilder.setSocketTimeout(MAX_TIMEOUT)
        // 设置从连接池获取连接实例的超时  
        configBuilder.setConnectionRequestTimeout(MAX_TIMEOUT)
        // 在提交请求之前 测试连接是否可用  
        configBuilder.setStaleConnectionCheckEnabled(true)
        requestConfig = configBuilder.build()

    }


    /**
     * @Description 获取参数内容
     * @Author      liangjilong
     * @Date        2017年6月6日 上午11:36:50
     * @param bodyParameters
     * @throws UnsupportedEncodingException 参数
     * @return String 返回类型
     */
    fun getConcatParams(bodyParameters: Map<String, String>, encoding :String?): String? {
        
        var content   = ""
        //?: elvis操作符(猫王),encoding? 表示if(encoding != null) encoding else 表示为空的时候默认就给 utf-8
        //如果  ?:  左侧表达式非空,elvis 操作符就返回其左侧表达式,否则返回右侧表达式。 请注意:当且仅当左侧为空时,才会对右侧表达式求值。
        var charset   = encoding ?: "utf-8"
        
        var builder   = StringBuilder()
        for (i in bodyParameters.entries) {
            //将参数解析为"name=tom&age=21"的模式
            builder.append(i.key).append("=").append(URLEncoder.encode(i.value.toString(), charset)).append("&")
        }
        if (builder.length > 1) {
            content = builder.substring(0,builder.length -1)
        }
        return content
    }

    
    //拼接参数同等getConcatParams方法,但是不encode编码
    fun paramsToQueryString(bodyParameters: Map<String, String>) :String? {
        return bodyParameters.entries.stream().map({e -> e.key + "=" + e.value}).collect(Collectors.joining("&"))
    }
    //拼接参数同等getConcatParams方法,但是encode编码
    fun paramsToQueryStringUrlencoded(bodyParameters: Map<String, String>,encoding :String?): String? {
        //?: elvis操作符(猫王),encoding? 表示if(encoding != null) encoding else 表示为空的时候默认就给 utf-8
        var charset   = encoding ?: "utf-8"
        return bodyParameters.entries.stream().map({ e -> e.key + "=" + URLEncoder.encode(e.value, charset) }).collect(Collectors.joining("&"))
    }


    /**
     * @Description 请求链接返回InputStream
     * @Author        liangjl
     * @Date        2018年6月12日 下午8:37:42
     * @param reqUrl
     * @return 参数
     * @return InputStream 返回类型
     */
    fun createHttp(reqUrl: String): InputStream ? {
        try {
            var url = URL(reqUrl) // 创建URL
            var urlconn = url.openConnection() // 试图连接并取得返回状态码
            urlconn.connect()
            //把Connection转换成HttpURLConnection,使用安全的类型转换,如果尝试转换不成功则返回  null 
            //var httpconn = urlconn as? HttpURLConnection //as?下面if条件写法完全不一样
            //if (httpconn?.responseCode != HttpURLConnection.HTTP_OK) {
            
            var httpconn = urlconn as HttpURLConnection 
            if (httpconn.responseCode != HttpURLConnection.HTTP_OK) {
                log.error("createHttp方法出错,无法连接到")
            } else {
                return urlconn.inputStream
            } 
        } catch (e: Exception) {
            log.error("createHttp方法出错,出错原因为:" + e.message)
        }
        return null
    }
    
    //创建http
    fun createHttp(reqUrl :String ,method: String,bodyParams:  String,
                   headers:Map<String,String>? ,charsetName :String?): String {

        val url  = URL(reqUrl)
        val conn = url.openConnection() as HttpURLConnection
        conn.requestMethod  = method
        conn.doOutput       = true
        conn.doInput        = true
        conn.useCaches      = false
        conn.connectTimeout = CONNECT_TIMEOUT
        conn.readTimeout    = CONNECT_TIMEOUT
        setRequestProperty(headers, conn)

        if (bodyParams.isNotEmpty()) {
            if (charsetName != null && !"".equals(charsetName)) {
                IoUtils.writerOutputStream(conn.outputStream, bodyParams, charsetName)
            } else {
                IoUtils.writerOutputStream(conn.outputStream, bodyParams)
            }
        }
        return IoUtils.getInputStream(conn.inputStream, charsetName)
    }
    
    
    /**
     * @Description 建立http请求链接支持SSL请求
     * @Author      liangjilong  
     * @Date        2017年6月6日 上午11:11:56  
     * @param reqUrl
     * @param requestMethod
     * @param outputStr
     * @param headerMap请求头属性,可以为空
     * @param sslVersion  支持https的版本参数(TLSv1, TLSv1.1  TLSv1.2)
     * @param bodyParameters  
     * @return String 返回类型   
     */
    fun createHttps(reqUrl: String, requestMethod: String, headerMap: Map<String, String>?,
                    sslVersion: String, bodyParameters: String?, encoding: String): String {
        
        var conn :HttpsURLConnection? = null
        try {
            //这行代码必须要在创建URL对象之前,因为先校验SSL的https请求通过才可以访问http
            var ssf  = SSLContextSecurity.createIgnoreVerifySSL(sslVersion)
                
            var url  = URL(reqUrl)
         
            conn = url.openConnection() as HttpsURLConnection
            // 从上述SSLContext对象中得到SSLSocketFactory对象
            conn.sslSocketFactory   = ssf
            conn.doOutput           = true
            conn.doInput            = true
            conn.useCaches          = false
            conn.connectTimeout     = CONNECT_TIMEOUT
            conn.readTimeout        = CONNECT_TIMEOUT
            
            addRequestProperty(headerMap, conn)
            
            // 设置请求方式(GET/POST)
            conn.requestMethod  = requestMethod
            // 当设置body请求参数
            if (bodyParameters != null && bodyParameters.isNotEmpty()) {  
                IoUtils.writerOutputStream(conn.outputStream, bodyParameters)
            } 

            if(conn.getResponseCode() == HttpStatus.SC_OK){
                // 从输入流读取返回内容
                return IoUtils.getInputStream(conn.inputStream,encoding)
            }else{
                return IoUtils.getInputStream(conn.inputStream,encoding)
            }
        
        } catch (e :Exception)  {
            log.error("https请求异常:{}请求链接" + reqUrl,e)
            return ""//请求系统频繁
        }finally{
            // 释放资源
            if(conn!=null){conn.disconnect()}
        }
    }
    
    
    /**
     * @Description 支持HttpClient的GET和POST请求,支持http和https协议
     * @Author  liangjl
     * @Date        2018年5月7日 下午10:05:28
     * @param reqUrl        请求链接
     * @param requestMethod 请求方法GET/POSt
     * @param bodyParameters 
     * @param encoding 编码
     * @param headerMap 请求头 
     * @return String 返回类型      
     */
   fun createHttp(reqUrl:String ,requestMethod :String, bodyParameters:Map<String,String>,
                     encoding :String?,headerMap:Map<String,String>?):String ?{  
        
        //这里是要转成Java的String,因为kotlin的String是没有忽略大小写这个方法
        //kotlin的==和equals是相同的都是比较字符串的值,而且===三个等号比较的是地址的值.
        var method  = requestMethod as java.lang.String
        
        if((reqUrl.startsWith("https") || reqUrl.contains("https") )  && method.equalsIgnoreCase("POST")) {
            
            return commonHttpClientPost(reqUrl, bodyParameters, encoding, headerMap, createHttpClient());  
            
        } else if((reqUrl.startsWith("http") || reqUrl.contains("http") )  && method.equalsIgnoreCase("POST")) {
            
            return commonHttpClientPost(reqUrl, bodyParameters, encoding, headerMap, HttpClients.createDefault());
            
        }else if((reqUrl.startsWith("https") || reqUrl.contains("https") )  && method.equalsIgnoreCase("GET")) {
            
            return commonHttpClientGet(reqUrl, bodyParameters, encoding, headerMap, createHttpClient());
            
        }else {
            return commonHttpClientGet(reqUrl, bodyParameters, encoding, headerMap, HttpClients.createDefault());  
        }
   }
    
    /**
    * @Description 发送Post请求  
    * @Author      liangjilong  
    * @Email       jilongliang@sina.com 
    * @Date        2017年10月31日 上午11:05:40  
    * @param reqUrl 请求Url
    * @param bodyParameters
    * @return 参数  
    * @return String 返回类型
     */
    fun createHttpPost(reqUrl:String, bodyParameters :List<BasicNameValuePair>):String  {
        var httpClient  = createHttpClient()
        var httpPost    = HttpPost(reqUrl)//创建HttpPost
        httpPost.config = requestConfig
        httpPost.entity = UrlEncodedFormEntity(bodyParameters, DEFAULT_ENCODING)//设置entity
        
        var httpResponse= httpClient.execute(httpPost)

        if (httpResponse.statusLine != null && httpResponse.statusLine.statusCode == HttpStatus.SC_OK) {
            var retMsg = EntityUtils.toString(httpResponse.entity, DEFAULT_ENCODING)
            if (retMsg != null && retMsg.isNotEmpty()) {
                return retMsg
            }
        } else {
            return ""
        }
        return ""
    }   
    
     /**
     * @Description commonHttpClientGet
     *  如:{'userName':'小梁','age':'100'}
     *  
     * @Author      liangjilong  
     * @Email       jilongliang@sina.com 
     * @Date        2017年8月1日 上午10:35:09  
     * @param reqUrl 请求链接
     * @param bodyParameters 请求参数
     * @param encoding 编码,不穿默认UTF-8
     * @param headerMap 头参数:如 application/x-www-form-urlencoded charset=utf-8
     * @param httpClient
     * @return 参数  
     * @return String 返回类型
     */
    private fun commonHttpClientGet(reqUrl :String, bodyParameters:Map<String, String>?,encoding :String?,
                            headers: Map<String, String>?, httpClient:CloseableHttpClient):String? {
        var restMsg: String = ""
        var response: CloseableHttpResponse ?= null
        try {
            //把参数转换成字符串
            var reqParamStr = EntityUtils.toString(setUrlEncodedFormEntity(bodyParameters, encoding))
            var httpGet : HttpGet?
            if (reqParamStr != null && reqParamStr.isNotEmpty()) {
                
                httpGet = HttpGet(reqUrl + "?" + reqParamStr)
                log.info(".commonHttpClientGet,reqUrl:" + reqUrl + "?" + reqParamStr)
                
            } else {
                httpGet = HttpGet(reqUrl)
            }
            /*设置请求头属性和值 */
            if (headers != null && headers.isNotEmpty()) {
            for (key in headers.keys) {
                    var value = headers.get(key)
                    httpGet.addHeader(key, value)
                }
            }
            httpGet.config = requestConfig
            
            response = httpClient.execute(httpGet)
            var entity = response.entity
            if (entity != null && response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                restMsg = EntityUtils.toString(entity, encoding)
                log.info(".commonHttpClientGet,请求链接为:" + reqUrl + ",Response Content: " + restMsg)
            } else {
                restMsg = ""
                log.error(".ommonHttpClientGet的entity对象为空" + reqUrl)
            }
        } catch (e: IOException) {
            log.error("commonHttpClientGet出现异常,异常信息为:" + e.message)
        } finally {
            if (response != null) {
                try {
                    EntityUtils.consume(response.getEntity())
                } catch (e: IOException) {
                }
            }
        }
        return restMsg
    }
    
    
    /**
     * @Description commonHttpClientPost
     * @Author      liangjilong  
     * @Email       jilongliang@sina.com 
     * @Date        2017年8月1日 上午10:34:43  
     * @param reqUrl
     * @param bodyParameters
     * @param contentType
     * @param encoding 编码,不传默认UTF-8
     * @param headerMap
     * @param httpClient
     * @return 参数  
     * @return String 返回类型
     */
    private fun commonHttpClientPost(reqUrl:String ,bodyParameters: Map<String, String> ?,  encoding:String?,
                             headerMap:Map<String, String>?,  httpClient:CloseableHttpClient):String {
        
        var restMsg :   String = ""
        var response:   CloseableHttpResponse? = null
        try {
            var httpPost    = setPostHeader(reqUrl, headerMap)/*设置请求头属性和值 */
            //把参数转换成字符串
            var reqParamStr = EntityUtils.toString(setUrlEncodedFormEntity(bodyParameters, encoding))
            httpPost.config = requestConfig
            setStringEntity(reqParamStr, encoding, httpPost)
            
            response = httpClient.execute(httpPost)
            
            var entity = response.getEntity()
            
            if (entity != null && response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                var buffer = IoUtils.getInputStream(entity.getContent(), encoding)
                log.info(".commonHttpClientPost,reqUrl:" + reqUrl + ",Response content : " + buffer.toString())
                return buffer.toString()//返回  
            } else {
                var buffer = IoUtils.getInputStream(entity.getContent(), encoding)
                log.error(".commonHttpClientPost的entity对象为空,请求链接为:" + reqUrl)
                return buffer.toString()//返回  
            }
        } catch (e: IOException) {
            log.error("commonHttpClientPost出现异常,异常信息为:" + e.message)
        } finally {
            if (response != null) {
                response.close()
            }
            httpClient.close()
        }
        return restMsg
    }  
   
    
     /**
     * @Description 创建UrlEncodedFormEntity参数对象实体 
     * 相当于处理请求链接的参数如: -->http://www.xxx.com/?username=yy&age=100
     * @Author      liangjilong  
     * @Email       jilongliang@sina.com 
     * @Date        2017年8月1日 上午10:34:25  
     * @param bodyParameters
     * @param encoding
     * @param reqParamStr
     * @throws IOException
     * @throws UnsupportedEncodingException 参数  
     * @return String 返回类型
     */
    fun setUrlEncodedFormEntity(bodyParameters :Map<String, String>?,  encoding:String?) :UrlEncodedFormEntity ? {
        
        if(bodyParameters != null && bodyParameters.isNotEmpty()){
            //封装请求参数  
            var params = ArrayList<NameValuePair>()
            for (entry in bodyParameters.entries) {  
                var key     = entry.key
                var `val`   = entry.value
                params.add(BasicNameValuePair(key,  `val`))
            }
            var charset   = encoding ?: "utf-8"
            return  UrlEncodedFormEntity(params,Charset.forName(charset))
        }
        return null
    }  
    /**
     *设置头属性
     */
    fun setRequestProperty(headers : Map<String, String>?, conn :HttpURLConnection) {
        
        if (headers != null && headers.isNotEmpty()) {
            for (key in headers.keys) {
                var value = headers.get(key)
                conn.setRequestProperty(key, value)
            }
        }
    }
    
    /**
     * @Description 添加请求属性
     * @Author  liangjl
     * @Date        2018年5月7日 下午9:51:54
     * @param headerMap
     * @param conn 参数
     * @return void 返回类型 
     */
    fun addRequestProperty(headers :Map<String,String>?,  conn :HttpsURLConnection) {
        /*设置请求头属性和值 */
        if (headers != null && headers.isNotEmpty()) {
            for (key in headers.keys) {
                 var value = headers.get(key)
                 //如:conn.addRequestProperty("Authorization","123456")
                 conn.addRequestProperty(key,value)
             }
        }
    }
    
    
    
     /**
     * @Description 创建Post的头参数处理
     * @Author  liangjl
     * @Date        2018年4月25日 上午11:38:37
     * @param reqUrl
     * @param headerMap
     * @return 参数
     * @return HttpPost 返回类型 
     */
     fun setPostHeader(reqUrl : String, headers :Map<String, String> ?) :HttpPost {
        var httpPost = HttpPost(reqUrl)  
        
        /*设置请求头属性和值 */
        if(headers != null &&headers.isNotEmpty()){
             for (key in  headers.keys) {
                 var value = headers.get(key)
                 //如: httpPost.addHeader("Content-Type", "application/json")  
                 httpPost.addHeader(key,value)
             }
        }
        return httpPost
    }  
    
     /**
     * @Description 设置setStringEntity参数
     * @Author  liangjl
     * @Date  2018年4月25日 下午12:23:57
     * @param bodyParameters
     * @param contentType 
     * @param encoding
     * @param httpPost 参数
     * @return void 返回类型 
     */
    fun setStringEntity(bodyParameters : Any?,  encoding :String?,  httpPost :HttpPost) {
         if (bodyParameters  != null) {
             // 解决中文乱码问题
             var charset      = encoding ?: "utf-8"
             var stringEntity = StringEntity(bodyParameters.toString(), charset)

             if (encoding != null && encoding.isNotEmpty()) {
                 stringEntity.setContentEncoding(encoding)
             }
             httpPost.setEntity(stringEntity)
         }
     }    
    
    //创建httpClient
     fun createHttpClient():CloseableHttpClient {
         val trustStrategy = object : TrustStrategy {
             override fun isTrusted(xcert509: Array<java.security.cert.X509Certificate>, arg1: String): Boolean {
                 return true
             }
         }
         var sslcontext = SSLContextBuilder().loadTrustMaterial(null,trustStrategy).build()

         var versions   = arrayOf( "TLSv1", "TLSv1.1", "TLSv1.2")
         var sslsf      = SSLConnectionSocketFactory(sslcontext,versions, null, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER)
         return HttpClients.custom().setSSLSocketFactory(sslsf).build()
    }
     
    /*private String  String java.lang.String(){
        
    }*/
    
    
    @JvmStatic //main测试
    fun main(args: Array<String>) {
        //var b = mapOf("age" to 23, "userName" to "ljl")
        
        var map3 = HashMap<String,String>()
    
        map3.put("age","23")
        map3.put("userName","寅务")

        //var p = getConcatParams(map3,null)
        //var p = paramsToQueryString(map3)
        //println(p)
        var reqUrl = "http://localhost:8080/rest/getBody"
        var method = "POST"
        var json = JSONObject()
        json.put("passWord","123456")
        json.put("userName","寅务")
        
        var header =   HashMap<String, String>()
        header.put("Content-Type", JSON_APPLICATION)
        
        var retMsg = createHttp(reqUrl,method,json.toJSONString(),header,"utf-8")
        println("retMsg=" +retMsg)

    }
}

/**
 * 针对@RequestBody请求的处理
 */
@ControllerAdvice
class LiveRequestBodyAdvice : RequestBodyAdvice {

    companion object {
        var DEFAULT_CHARSET = "UTF-8";
        private val logger: Logger = LoggerFactory.getLogger(LiveRequestBodyAdvice::class.java)
    }

    override fun handleEmptyBody(body: Any?, inputMessage: HttpInputMessage?, parameter: MethodParameter?, targetType: Type?, converterType: Class<out HttpMessageConverter<*>>?): Any? {
        return body;
    }

    override fun afterBodyRead(body: Any?, inputMessage: HttpInputMessage?, parameter: MethodParameter?, targetType: Type?, converterType: Class<out HttpMessageConverter<*>>?): Any? {
        return body;
    }

    override fun beforeBodyRead(inputMessage: HttpInputMessage?, parameter: MethodParameter?, targetType: Type?, converterType: Class<out HttpMessageConverter<*>>?): HttpInputMessage? {
        var body    = IOUtils.toString(inputMessage?.getBody(), DEFAULT_CHARSET) as java.lang.String;
        var headers = inputMessage!!.getHeaders();
        var bis     = ByteArrayInputStream(body.getBytes(DEFAULT_CHARSET));
        logger.info("request body params : {}", body);

        return InputMessage(headers, bis);
    }

    override fun supports(methodParameter: MethodParameter?, targetType: Type?, converterType: Class<out HttpMessageConverter<*>>?): Boolean {
        return true;
    }


    //httpInput输出流信息,这里的重写方法必须要加上open,因为kotlin默认是private,不加上会报HttpHeaders defined in 
    open class InputMessage : HttpInputMessage {
        private var headers: HttpHeaders
        private var body: InputStream
        
        //构造方法
        constructor(headers: HttpHeaders, body: InputStream) {
            this.headers = headers;
            this.body = body;
        }
        open override fun getBody(): InputStream    = this.body
        open override fun getHeaders(): HttpHeaders = this.headers
    }

}

@RestControllerAdvice
@Slf4j
@Order(Integer.MIN_VALUE)
class LiveRespBodyAdvice : ResponseBodyAdvice<Any> {
    
    companion object {
        //const 关键字用来修饰常量,且只能修饰  val,不能修饰var,  companion object 的名字可以省略,可以使用 Companion来指代
         const val LIFE_PACKAGE = "com.flong.kotlin";
    }
    
    //Class<out HttpMessageConverter<*>>?) 相当于Java的  Class<? extends HttpMessageConverter<?>>
    //list :ArrayList<in Fruit>,相当于Java的 ArrayList< ? super Fruit> 
    //*,相当于Java的?
    override fun supports(methodParameter: MethodParameter?, converterType: Class<out HttpMessageConverter<*>>?): Boolean {
        //处理类型 
        var className   = methodParameter?.getContainingClass()?.name;
        var sw          = className?.startsWith(LIFE_PACKAGE);
        var eaf         = ErrorResp::class.java.isAssignableFrom(methodParameter?.getParameterType());
        var laf         = LiveResp::class.java.isAssignableFrom(methodParameter?.getParameterType());
        var saf         = String::class.java.isAssignableFrom(methodParameter?.getParameterType());
        
        return (sw==true && !eaf && !laf && !saf);
    }
    
    override fun beforeBodyWrite(body: Any?, returnType: MethodParameter?, selectedContentType: MediaType?, selectedConverterType: Class<out HttpMessageConverter<*>>?, request: ServerHttpRequest?, response: ServerHttpResponse?): Any? {
        //?: elvis操作符(猫王),age?.toInt()表示if(age!=null) age.toInt else 返回 默认就给10
        //var path = request?.getURI()?.getPath();
        if(body != null){
            return LiveResp(body);
        }else{
            return LiveResp("");
        }
         
    }
}


@Slf4j
@RestControllerAdvice
class ExceptionAdvice{

    companion object {
        private val log: Logger = LoggerFactory.getLogger(ExceptionAdvice::class.java)
    }
    
    @ExceptionHandler(HttpRequestMethodNotSupportedException::class)
    fun onException(e : HttpRequestMethodNotSupportedException, request: HttpServletRequest): ErrorResp? {
        var uri = request.getRequestURI();
        
        createLog(e, uri, "找不到请求的方法");
        
        return ErrorResp(CommMsgCode.NOT_SUPPORTED.code!!, CommMsgCode.DB_ERROR.message!!)
    }

    //is 相当于Java的 instanceof ,as就是强制转换(对象)
    @ExceptionHandler(Exception::class)
    fun onException(e: Exception, request: HttpServletRequest): ErrorResp? {
        
        var uri     = request.getRequestURI();
        var params  = JSONObject.toJSONString(request.getParameterMap());
        if (e is SQLException || e is DataAccessException) {
            createLog(e, uri, params);
            return ErrorResp(CommMsgCode.DB_ERROR.code!!, CommMsgCode.DB_ERROR.message!!)

        } else if (e is BaseException) {
            var be = e as BaseException
            log.error("uri:{},params:{},code:{},message:{}", uri, params)
            return ErrorResp(be.code,be.msg)
            
        } else if (e is MissingServletRequestParameterException
                || e is BindException
                || e is ConstraintViolationException
                || e is TypeMismatchException
                || e is ServletRequestBindingException) {

            createLog(e, uri, params);
            
            return ErrorResp(CommMsgCode.PARAM_ERROR.code!!, CommMsgCode.PARAM_ERROR.message!!)
        } else {
            return ErrorResp(CommMsgCode.SERVER_ERROR.code!!, CommMsgCode.SERVER_ERROR.message!!)
        }
    }
    
    
    //错误信息
    fun createErrorResp(msgCode : MsgCode,  message : String?) :ErrorResp {
        var msg = "";
        if(ObjectUtil().isNotEmpty(message)){
            msg = message +"";
        }else{
            msg = msgCode.getMessage();
        }
        return ErrorResp(msgCode.getCode(), msg);
    }

    //打印log
    fun createLog(e: Exception, uri: String, params: String) {
        log.error("uri:" + uri + ",params:" + params, e);
    }
    
}

/**
 * Jsoup 防止 XSS 攻击 工具类
 */
object Jsoup2XssUtils {
    /**
     * 使用自带的 basicWithImages 白名单
     * 允许的便签有 a,b,blockquote,br,cite,code,dd,dl,dt,em,i,li,ol,p,pre,q,small,span,
     * strike,strong,sub,sup,u,ul,img
     * 以及 a 标签的 href,img 标签的 src,align,alt,height,width,title 属性
     */
    private val whitelist: Whitelist = Whitelist.basicWithImages()

    /**
     * 配置过滤化参数 不对代码进行格式化
     */
    private val outputSettings: Document.OutputSettings = Document.OutputSettings().prettyPrint(false)
    init {
        // 富文本编辑时一些样式是使用style来进行实现的
        // 比如红色字体 style="color:red;"
        // 所以需要给所有标签添加 style 属性
        whitelist.addAttributes(":all", "style")
    }
    /**
     * 清除
     * @param content 内容
     */
    fun clean(content: String): String {
        var contentStr: String = content
        if (contentStr.isNotBlank()) {
            contentStr = content.trim { it <= ' ' }
        }
        return Jsoup.clean(contentStr, "", whitelist, outputSettings)
    }
}
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增id',
  `user_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '用户Id主键,IdWork生成',
  `user_name` varchar(255) DEFAULT '' COMMENT '用户名',
  `pass_word` varchar(255) DEFAULT '' COMMENT '密码',
  `is_deleted` int(2) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除,0-不删除,1-删除',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`user_id`),
  UNIQUE KEY `id` (`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

--  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',是mysql5.6以上的版本

// 20190319105420
// 访问路径 :http://localhost:8080/rest/listPage
//访问结果
{
  "code": "200",
  "data": {
    "page": "1",
    "pageSize": "20",
    "records": [
      {
        "createTime": "2019-01-04 16:01:34",
        "passWord": "123456",
        "userId": "1081098298906394625",
        "userName": "liangjl"
      },
      {
        "createTime": "2019-01-04 16:14:50",
        "passWord": "123456",
        "userId": "1081101635991232513",
        "userName": "liangjl"
      }
    ],
    "total": "2",
    "totalPage": "1"
  },
  "msg": "",
  "timestamp": "1552964058194"
}
GIF.gif

十四、为什么记录笔记

十五、其他说明

上一篇下一篇

猜你喜欢

热点阅读