springboot+mybatisPlus代碼層級梳理

## 代碼層級梳理

#### 代碼層級

![UTOOLS1588253419016.png](https://johnnycc.oss-cn-beijing.aliyuncs.com/UTOOLS1588253419016.png)

#### annotation層

![UTOOLS1588253490167.png](https://johnnycc.oss-cn-beijing.aliyuncs.com/UTOOLS1588253490167.png)

###### ExcelVoAttribut

主要用于寫@ExcelVoAttribute注解,可以更靈活的配置Excel數據表導出的一些基本配置

###### LogDirection

主要用于寫@LogDirection注解,注解里包含一個字段(接口描述),通過接口添加這個注解,填寫上接口的功能,當用戶在訪問接口時候,可以利用aop技術,把用戶的操作記到日志里

#### aspect層

![UTOOLS1588253846594.png](https://johnnycc.oss-cn-beijing.aliyuncs.com/UTOOLS1588253846594.png)

###### LogDrectionAspect

主要定義了aop切點的位置和增強的方法

```java

/**

* @Author Johnny

* @Date 2020/4/16

* @Version 1.0

*/

@Aspect

@Component

@Slf4j

public class LogDirectionAspect {

? ? @Pointcut("@annotation(logDirection)")

? ? public void doLogDirection(LogDirection logDirection){

? ? }

? ? @Around(value = "doLogDirection(logDirection)",argNames = "pjp,logDirection")

? ? public Object doBefore(ProceedingJoinPoint pjp,LogDirection logDirection) throws? Throwable{

? ? ? ? if (!"".equals(logDirection.direction())) {

? ? ? ? ? ? log.info(logDirection.direction()+"接口被調用");

? ? ? ? ? ? return? pjp.proceed();

? ? ? ? }

? ? ? ? return "接口信息不規范";

? ? }

}

```

@Pointcut定義切點,此處定義切點為使用@LogDirection的方法

@Around定義增強的方法,通過around的參數ProceedingJoinPoint可以很好的控制程序能不能繼續執行下一步,在權限判定發揮著比較重要的作用

拓展:

利用下面的這種方法,需要在xml文件中配置bean實例,當然也可以在方法中通過手動掃包的方法獲得bean實例進行操作

```

/**

* @Author Johnny

* @Date 2020/4/16

* @Version 1.0

*/

@Aspect

public class MyAdvice {

? ? /**

? ? * 配置切點

? ? */

? ? @Pointcut("execution(* com.soft1851.spring.web.dao..*.insert*(..))")

? ? public void pointCut() {

? ? }

? ? /**

? ? * 配置前置增強

? ? */

? ? @Before("MyAdvice.pointCut()")

? ? public void beforeMethod() {

? ? ? ? System.out.println("等待數據插入");

? ? }

? ? /**

? ? * 配置后置增強

? ? */

? ? @AfterReturning("execution(* com.soft1851.spring.web.dao..*.*(..))")

? ? public void afterMethod(){

? ? ? ? System.out.println("關閉數據庫");

? ? }

}

```

xml配置實例

```

<bean name="forumDao" class="com.soft1851.spring.web.dao.impl.ForumDaoImpl"/>

? ? <bean name="myAdvice" class="com.soft1851.spring.web.interceptor.MyAdvice"/>

? ? <aop:aspectj-autoproxy/>

```

#### common層

![UTOOLS1588254433232.png](https://johnnycc.oss-cn-beijing.aliyuncs.com/UTOOLS1588254433232.png)

ResponseResult和ResultCode

這兩個類可以將響應體統一進行封裝,將接口的響應體規范化,使前端獲取數據也更加友好

貼下代碼,防止以后找不到

ResponseResult類

```

package com.soft1851.music.admin.common;

import lombok.Data;

import java.io.Serializable;

/**

* @author Johnny

* @Date: 2020/4/21 19:52

* @Description:

*/

@Data

public class ResponseResult implements Serializable {

? ? private static final long serialVersionUID = -3948389268046368059L;

? ? private Integer code;

? ? private String msg;

? ? private Object data;

? ? private ResponseResult() {

? ? }

? ? public ResponseResult(Integer code, String msg) {

? ? ? ? this.code = code;

? ? ? ? this.msg = msg;

? ? }

? ? public static ResponseResult success() {

? ? ? ? ResponseResult result = new ResponseResult();

? ? ? ? result.setResultCode(ResultCode.SUCCESS);

? ? ? ? return result;

? ? }

? ? public static ResponseResult success(Object data) {

? ? ? ? ResponseResult result = new ResponseResult();

? ? ? ? result.setResultCode(ResultCode.SUCCESS);

? ? ? ? result.setData(data);

? ? ? ? return result;

? ? }

? ? public static ResponseResult failure(ResultCode resultCode) {

? ? ? ? ResponseResult result = new ResponseResult();

? ? ? ? result.setResultCode(resultCode);

? ? ? ? return result;

? ? }

? ? public static ResponseResult failure(ResultCode resultCode, Object data) {

? ? ? ? ResponseResult result = new ResponseResult();

? ? ? ? result.setResultCode(resultCode);

? ? ? ? result.setData(data);

? ? ? ? return result;

? ? }

? ? public void setResultCode(ResultCode code) {

? ? ? ? this.code = code.code();

? ? ? ? this.msg = code.message();

? ? }

}

```

ResultCode類(枚舉)

```

package com.soft1851.music.admin.common;

/**

* @author Johnny

* @Date: 2020/4/21 19:52

* @Description:

*/

public enum ResultCode {

? ? /* 成功狀態碼 */

? ? SUCCESS(1, "成功"),

? ? /* 通用錯誤:10001-19999 */

? ? PARAM_IS_INVALID(10001, "參數無效"),

? ? PARAM_IS_BLANK(10002, "參數為空"),

? ? PARAM_TYPE_BIND_ERROR(10003, "參數類型錯誤"),

? ? PARAM_NOT_COMPLETE(10004, "參數缺失"),

? ? HTTP_METHOD_ERROR(10005, "請求方法錯誤"),

? ? HTTP_METHOD_NOT_ALLOWED(10006, "請求方法不允許"),

? ? HTTP_NOT_FOUND(10007, "請求地址錯誤"),

? ? BOUND_STATEMENT_NOT_FOUND(10008, "Mybatis未綁定"),

? ? CONNECTION_ERROR(10009, "網絡連接錯誤"),

? ? ARITHMETIC_ERROR(100010, "計算錯誤"),

? ? /* 用戶錯誤:20001-29999*/

? ? USER_NOT_SIGN_IN(20001, "請先登錄"),

? ? USER_PASSWORD_ERROR(20002, "密碼錯誤"),

? ? USER_ACCOUNT_ERROR(20003, "賬號錯誤"),

? ? USER_VERIFY_CODE_ERROR(20004, "驗證碼錯誤"),

? ? USER_CODE_TIMEOUT(20005, "驗證碼失效"),

? ? USER_ACCOUNT_FORBIDDEN(20006, "賬號已被禁用"),

? ? USER_SIGN_UP_FAIL(20007, "用戶注冊失敗"),

? ? USER_SIGN_IN_FAIL(20008, "用戶登錄失敗"),

? ? USER_NOT_FOUND(20009, "用戶不存在"),

? ? USER_NO_AUTH(20019, "用戶權限不足"),

? ? USER_TOKEN_EXPIRES(200010, "Token已過期"),

? ? /* 業務錯誤:30001-39999 */

? ? SMS_ERROR(30001, "短信業務出現問題"),

? ? UPLOAD_ERROR(30002, "上傳文件業務出現問題"),

? ? CAPTCHA_ERROR(30003, "驗證碼業務出現問題"),

? ? /* 數據錯誤:40001-49999 */

? ? RESULT_CODE_DATA_NONE(50001, "數據未找到"),

? ? DATA_IS_WRONG(50002, "數據有誤"),

? ? DATA_ALREADY_EXISTED(50003, "數據已存在"),

? ? DATABASE_ERROR(50004, "數據庫操作異常"),

? ? /* 服務器或系統錯誤:50001-599999 */

? ? SERVER_ERROR(50000, "服務器錯誤,請稍后重試"),

? ? SYSTEM_ERROR(40001, "系統錯誤,請稍后重試"),

? ? /* 接口錯誤:60001-69999 */

? ? INTERFACE_INNER_INVOKE_ERROR(60001, "內部系統接口調用異常"),

? ? INTERFACE_OUTER_INVOKE_ERROR(60002, "外部系統接口調用異常"),

? ? INTERFACE_FORBID_VISIT(60003, "該接口禁止訪問"),

? ? INTERFACE_ADDRESS_INVALID(60004, "接口地址無效"),

? ? INTERFACE_REQUEST_TIMEOUT(60005, "接口請求超時"),

? ? INTERFACE_EXCEED_LOAD(60006, "接口負載過高"),

? ? /* 權限錯誤:70001-79999 */

? ? PERMISSION_NO_ACCESS(70001,"無訪問權限");

? ? private Integer code;

? ? private String message;

? ? ResultCode(Integer code, String message) {

? ? ? ? this.code = code;

? ? ? ? this.message = message;

? ? }

? ? public Integer code() {

? ? ? ? return this.code;

? ? }

? ? public String message() {

? ? ? ? return this.message;

? ? }

? ? public static String getMessage(String name) {

? ? ? ? for (ResultCode item : ResultCode.values()) {

? ? ? ? ? ? if (item.name().equals(name)) {

? ? ? ? ? ? ? ? return item.message;

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? return name;

? ? }

? ? public static Integer getCode(String name) {

? ? ? ? for (ResultCode item : ResultCode.values()) {

? ? ? ? ? ? if (item.name().equals(name)) {

? ? ? ? ? ? ? ? return item.code;

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? return null;

? ? }

? ? @Override

? ? public String toString() {

? ? ? ? return this.name();

? ? }

}

```

#### config層

![UTOOLS1588254579776.png](https://johnnycc.oss-cn-beijing.aliyuncs.com/UTOOLS1588254579776.png)

###### CaptchaConfig

主要是為kaptcha谷歌驗證碼的一些基本信息進行配置,比如字體,驗證碼位數等等

附上一些kaptha的基本配置信息

| **Constant**? ? ? ? ? ? ? ? ? ? | **描述**? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? | **默認值**? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |

| -------------------------------- | ------------------------------------------------------------ | ----------------------------------------------------- |

| kaptcha.border? ? ? ? ? ? ? ? ? | 圖片邊框,合法值:yes , no? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? | yes? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |

| kaptcha.border.color? ? ? ? ? ? | 邊框顏色,合法值: r,g,b (and optional alpha) 或者 white,black,blue. | black? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |

| kaptcha.border.thickness? ? ? ? | 邊框厚度,合法值:>0? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? | 1? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |

| kaptcha.image.width? ? ? ? ? ? ? | 圖片寬? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? | 200? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |

| kaptcha.image.height? ? ? ? ? ? | 圖片高? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? | 50? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |

| kaptcha.producer.impl? ? ? ? ? ? | 圖片實現類? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? | com.google.code.kaptcha.impl.DefaultKaptcha? ? ? ? ? |

| kaptcha.textproducer.impl? ? ? ? | 文本實現類? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? | com.google.code.kaptcha.text.impl.DefaultTextCreator? |

| kaptcha.textproducer.char.string | 文本集合,驗證碼值從此集合中獲取? ? ? ? ? ? ? ? ? ? ? ? ? ? | abcde2345678gfynmnpwx? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |

| kaptcha.textproducer.char.length | 驗證碼長度? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? | 5? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |

| kaptcha.textproducer.font.names? | 字體? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? | Arial, Courier? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |

| kaptcha.textproducer.font.size? | 字體大小? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? | 40px.? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |

| kaptcha.textproducer.font.color? | 字體顏色,合法值: r,g,b? 或者 white,black,blue.? ? ? ? ? ? | black? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |

| kaptcha.textproducer.char.space? | 文字間隔? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? | 2? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |

| kaptcha.noise.impl? ? ? ? ? ? ? | 干擾實現類? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? | com.google.code.kaptcha.impl.DefaultNoise? ? ? ? ? ? |

| kaptcha.noise.color? ? ? ? ? ? ? | 干擾 顏色,合法值: r,g,b 或者 white,black,blue.? ? ? ? ? ? | black? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |

| kaptcha.obscurificator.impl? ? ? | 圖片樣式:? 水紋com.google.code.kaptcha.impl.WaterRipple? 魚眼com.google.code.kaptcha.impl.FishEyeGimpy? 陰影com.google.code.kaptcha.impl.ShadowGimpy | com.google.code.kaptcha.impl.WaterRipple? ? ? ? ? ? ? |

| kaptcha.background.impl? ? ? ? ? | 背景實現類? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? | com.google.code.kaptcha.impl.DefaultBackground? ? ? ? |

| kaptcha.background.clear.from? ? | 背景顏色漸變,開始顏色? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? | light grey? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |

| kaptcha.background.clear.to? ? ? | 背景顏色漸變, 結束顏色? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? | white? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |

| kaptcha.word.impl? ? ? ? ? ? ? ? | 文字渲染器? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? | com.google.code.kaptcha.text.impl.DefaultWordRenderer |

| kaptcha.session.key? ? ? ? ? ? ? | session key? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? | KAPTCHA_SESSION_KEY? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |

| kaptcha.session.date? ? ? ? ? ? | session date? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? | KAPTCHA_SESSION_DATE? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |

###### CorsConfig

主要是為跨域做一些基本的配置,學習階段后端必備

代碼展示,防止以后忘記

```

/**

* @Author Johnny

* @Date 2020/4/16

* @Version 1.0

*/

@Configuration

public class CorsConfig {

? ? @Bean

? ? public FilterRegistrationBean<CorsFilter> corsFilter() {

? ? ? ? UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

? ? ? ? CorsConfiguration config = new CorsConfiguration();

? ? ? ? config.setAllowCredentials(true);

? ? ? ? //放行所有跨域的客戶端domain

? ? ? ? config.addAllowedOrigin("*");

? ? ? ? //允許的請求方法列表

? ? ? ? String[] requestMethods = {"GET", "POST", "PUT", "DELETE", "OPTIONS"};

? ? ? ? List<String> allowedRequestMethods = Arrays.asList(requestMethods);

? ? ? ? config.setAllowedMethods(allowedRequestMethods);

? ? ? ? //允許的客戶端請求頭列表

? ? ? ? String[] requestHeaders = {"x-requested-with", "Content-Type", "Authorization"};

? ? ? ? List<String> allowedHeaders = Arrays.asList(requestHeaders);

? ? ? ? config.setAllowedHeaders(allowedHeaders);

? ? ? ? //允許的響應頭列表

? ? ? ? String[] responseHeaders = {"Access-Control-Expose-Headers", "Authorization"};

? ? ? ? List<String> allowedExposedHeaders = Arrays.asList(responseHeaders);

? ? ? ? config.setExposedHeaders(allowedExposedHeaders);

? ? ? ? source.registerCorsConfiguration("/**", config);

? ? ? ? FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));

? ? ? ? // 這個順序很重要,設置在最前

? ? ? ? bean.setOrder(0);

? ? ? ? return bean;

? ? }

}

```

###### MybatisPlusConfig

主要是為mybatisplus做一些基本配置(如果要有分頁查詢數據,則這個配置文件必須要有)

```

/**

* @Author Johnny

* @Date 2020/4/16

* @Version 1.0

*/

@EnableTransactionManagement

@Configuration

@MapperScan("com.baomidou.cloud.service.*.mapper*")

public class MybatisPlusConfig {

? ? @Bean

? ? public PaginationInterceptor paginationInterceptor() {

? ? ? ? PaginationInterceptor paginationInterceptor = new PaginationInterceptor();

? ? ? ? //設置請求的頁面大于最大頁操作,true調回到首頁,false繼續請求,默認為false

? ? ? ? paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));

? ? ? ? return paginationInterceptor;

? ? }

}

```

###### MySqlGenerator

代碼生成器,功能強大無比,根據數據表,一鍵生成各種層級,各種實體

```

/**

* @ClassName MySqlGenerator

* @Description 代碼生成器

* @Author Johnny

* @Date 2020/4/16

* @Version 1.0

*/

@Slf4j

public class MySqlGenerator {

? ? public static void main(String[] args) {

? ? ? ? //全局策略配置

? ? ? ? GlobalConfig config = new GlobalConfig();

? ? ? ? //獲得當前項目根路徑d:/dev/SpringBoot/spring-boot-learning

? ? ? ? String projectPath = System.getProperty("user.dir");

? ? ? ? config.setActiveRecord(true)

? ? ? ? ? ? ? ? //作者注釋

? ? ? ? ? ? ? ? .setAuthor("Johnny")

? ? ? ? ? ? ? ? //代碼生成輸出路徑

? ? ? ? ? ? ? ? .setOutputDir(projectPath + "/src/main/java")

? ? ? ? ? ? ? ? //覆蓋已有文件,默認false

? ? ? ? ? ? ? ? .setFileOverride(true)

? ? ? ? ? ? ? ? //是否打開輸出目錄窗口。默認true

? ? ? ? ? ? ? ? .setOpen(false)

? ? ? ? ? ? ? ? //開啟swagger2模式

? ? ? ? ? ? ? ? //.setSwagger2(true)

? ? ? ? ? ? ? ? //開啟ActiveRecord模式

? ? ? ? ? ? ? ? .setActiveRecord(true)

? ? ? ? ? ? ? ? //mapper添加restMap

? ? ? ? ? ? ? ? .setBaseResultMap(true)

? ? ? ? ? ? ? ? //mapper添加Base_Column_List

? ? ? ? ? ? ? ? .setBaseColumnList(true)

? ? ? ? ? ? ? ? //時間類型對應策略,默認time_pack

? ? ? ? ? ? ? ? //.setDateType(DateType.TIME_PACK)

? ? ? ? ? ? ? ? //相關包中的接口和類名后綴

? ? ? ? ? ? ? ? .setMapperName("%sMapper")

? ? ? ? ? ? ? ? .setServiceName("%sService")

? ? ? ? ? ? ? ? .setServiceImplName("%sServiceImpl");

? ? ? ? //數據庫表配置,通過該配置,可指定需要生成哪些表或者排除哪些表

? ? ? ? StrategyConfig strategyConfig = new StrategyConfig();

? ? ? ? //是否大寫命名

? ? ? ? strategyConfig.setCapitalMode(true)

? ? ? ? ? ? ? ? //是否跳過視圖

? ? ? ? ? ? ? ? .setSkipView(true)

? ? ? ? ? ? ? ? //數據庫表映射到實體的命名策略為駝峰式

? ? ? ? ? ? ? ? .setNaming(NamingStrategy.underline_to_camel)

? ? ? ? ? ? ? ? //生成表,可以寫多個,如果不加參數,默認為所有表

? ? ? ? ? ? ? ? .setInclude()

? ? ? ? ? ? ? ? .setEntityBuilderModel(true)

? ? ? ? ? ? ? ? .setEntityLombokModel(true)

? ? ? ? ? ? ? ? .setRestControllerStyle(true)

? ? ? ? ? ? ? ? .setEntityTableFieldAnnotationEnable(true);

? ? ? ? //包名配置

? ? ? ? PackageConfig packageConfig = new PackageConfig();

? ? ? ? //父包名

? ? ? ? packageConfig.setParent("com.soft1851.music.admin")

? ? ? ? ? ? ? ? .setMapper("mapper")

? ? ? ? ? ? ? ? .setService("service")

? ? ? ? ? ? ? ? .setController("controller")

? ? ? ? ? ? ? ? .setXml("xml")

? ? ? ? ? ? ? ? .setEntity("entity");

? ? ? ? //數據源配置

? ? ? ? DataSourceConfig dataSourceConfig = new DataSourceConfig();

? ? ? ? dataSourceConfig.setDbType(DbType.MYSQL)

? ? ? ? ? ? ? ? .setDriverName("com.mysql.jdbc.Driver")

? ? ? ? ? ? ? ? .setUrl("jdbc:mysql://rm-m5ee476bu350735gjeo.mysql.rds.aliyuncs.com:3306/cloud_music?useUnicode=true&characterEncoding=utf8&useSSL=false&autoReconnect=true")

? ? ? ? ? ? ? ? .setUsername("root")

? ? ? ? ? ? ? ? .setPassword("XuNiit_#");

? ? ? ? AutoGenerator autoGenerator = new AutoGenerator();

? ? ? ? autoGenerator.setGlobalConfig(config)

? ? ? ? ? ? ? ? .setStrategy(strategyConfig)

? ? ? ? ? ? ? ? .setDataSource(dataSourceConfig)

? ? ? ? ? ? ? ? .setTemplateEngine(new FreemarkerTemplateEngine())

? ? ? ? ? ? ? ? .setPackageInfo(packageConfig);

? ? ? ? autoGenerator.execute();

? ? ? ? log.info("=============代碼生成成功================");

? ? }

}

```

###### RedisConfig

主要對RedisConfig緩存數據庫進行一些基本配置

```

/**

* @author Johnny

* @Date: 2020/4/21 20:45

* @Description:

*/

@Configuration

@EnableCaching

public class RedisConfig {

? ? @Bean

? ? CacheManager cacheManager(RedisConnectionFactory connectionFactory) {

? ? ? ? //初始化RedisCacheWrite

? ? ? ? RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);

? ? ? ? RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig();

? ? ? ? //設置默認過期時間為1天

? ? ? ? defaultCacheConfig.entryTtl(Duration.ofDays(1));

? ? ? ? //初始化RedisCacheManger

? ? ? ? return new RedisCacheManager(redisCacheWriter, defaultCacheConfig);

? ? }

? ? @Bean

? ? public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {

? ? ? ? RedisTemplate<Object,Object> template = new RedisTemplate<>();

? ? ? ? template.setConnectionFactory(connectionFactory);

? ? ? ? //使用Jackson2JsonRedisSerializer來序列化和反序列化redis的value值(默認使用JDK的序列化方式)

? ? ? ? Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<Object>(Object.class);

? ? ? ? ObjectMapper mapper = new ObjectMapper();

? ? ? ? mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);

? ? ? ? //使用stringRedisSerializer來序列化和反序列化redis的value值

? ? ? ? template.setKeySerializer(new StringRedisSerializer());

? ? ? ? template.afterPropertiesSet();

? ? ? ? return template;

? ? }

? ? @Bean

? ? public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {

? ? ? ? StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();

? ? ? ? stringRedisTemplate.setConnectionFactory(factory);

? ? ? ? return stringRedisTemplate;

? ? }

}

```

###### WebConfig

這個類中主要配置了攔截器,可以對部分頁面進行統一攔截,當然這個只是統一的攔截,具體的攔截所要進行的操作,還在下文

```

/**

* @author Johnny

* @Date: 2020/4/21 20:58

* @Description:

*/

@Configuration

public class WebConfig implements WebMvcConfigurer {

? ? @Resource

? ? private LoginInterceptor loginInterceptor;

? ? @Resource

? ? private JwtInterceptor jwtInterceptor;

? ? /**

? ? * 添加攔截器配置

? ? */

? ? @Override

? ? public void addInterceptors(InterceptorRegistry registry) {

? ? ? ? //攔截路徑可自行配置多個 可用 ,分隔開

? ? ? ? registry.addInterceptor(loginInterceptor).addPathPatterns("/sysAdmin/login").excludePathPatterns("/**").excludePathPatterns("/static/**");

//? ? ? ? registry.addInterceptor(jwtInterceptor).addPathPatterns("/**").excludePathPatterns("/sysAdmin/login", "/captcha","/songList/page").excludePathPatterns("/static/**");

? ? }

}

```

#### controller層

controller層暫時帶過,后面有時間的話把controller層常用注解匯總整理下

#### dto層

目前還沒有將entity、vo、dto統一放到domain里,dto里主要是用戶請求接口時需要傳的參數,vo主要時后端返回前端展示的參數,而entity就是我們和數據庫表所吻合的類。舉例:我們數據庫中的用戶表的字段肯定不只時賬號和密碼,會有一些其他字段,比如加密鹽、用戶基本信息等等,但是用戶請求登錄接口時,只需要傳賬號和密碼就可以了,此時此刻,為了防止資源的浪費,我們就定義一個dto來接收前端傳來的參數。當用戶進入主界面時,查看個人信息,后端并不需要將用戶id、加密鹽等私密信息展示到前端,此時,同樣為了節省資源,我們定義vo視圖對象,將前端需要的參數放進去,再傳入前端。

#### entity層

entity層主要是將數據庫中的表一對一的寫好,此處是直接用代碼生成器生成

#### exception

![image-20200430222108856](C:\Users\Jack\AppData\Roaming\Typora\typora-user-images\image-20200430222108856.png)

###### CustomException

定義全局異常處理,使用時直接throws CustomException就好

```

/**

* @author Johnny

* @Date: 2020/4/22 10:48

* @Description:

*/

public class CustomException extends? RuntimeException {

? ? protected ResultCode resultCode;

? ? public CustomException(String msg, ResultCode resultCode) {

? ? ? ? super(msg);

? ? ? ? this.resultCode = resultCode;

? ? }

? ? public ResultCode getResultCode() {

? ? ? ? return resultCode;

? ? }

}

```

###### GlobalException

也是全局異常處理,但和上面略有區別,非手動throw,spring在遇到異常時,會自動來這個類里查找,如果已經有配置過的,就會按照配置要求輸出內容

```

/**

* @author Johnny

* @Date: 2020/4/30 18:45

* @Description:

*/

@ControllerAdvice

public class GlobalException {

? ? @ExceptionHandler(ConstraintViolationException.class)

? ? ResponseResult handleConstraintViolationException(ConstraintViolationException e) {

? ? ? ? return ResponseResult.failure(ResultCode.DATA_IS_WRONG);

? ? }

}

```

###### JwtException

同CustomException類格式一樣,但這個側重于處理Jwt模塊的異常

#### filter

![UTOOLS1588256707418.png](https://johnnycc.oss-cn-beijing.aliyuncs.com/UTOOLS1588256707418.png)

###### ChannelFilter

```

/**

* @author Johnny

* @Date: 2020/4/21 20:19

* @Description:

*/

@WebFilter(urlPatterns = "/*",filterName = "channelFilter")

public class ChannelFilter implements Filter {

? ? @Override

? ? public void init(FilterConfig filterConfig) throws ServletException {

? ? }

? ? @Override

? ? public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

? ? ? ? ServletRequest requestWrapper = null;

? ? ? ? if (servletRequest instanceof HttpServletRequest) {

? ? ? ? ? ? requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest);

? ? ? ? }

? ? ? ? if (requestWrapper == null) {

? ? ? ? ? ? filterChain.doFilter(servletRequest,servletResponse);

? ? ? ? }else{

? ? ? ? ? ? filterChain.doFilter(requestWrapper, servletResponse);

? ? ? ? }

? ? }

? ? @Override

? ? public void destroy() {

? ? }

}

```

###### LoginFilter

這個類主要針對用戶登錄進行過濾

```

/**

* @author Johnny

* @Date: 2020/4/26 10:46

* @Description:

*/

@Slf4j

@WebFilter(urlPatterns = "/login",filterName = "LoginFilter")

public class LoginFilter implements Filter {

? ? @Override

? ? public void init(FilterConfig filterConfig) throws ServletException {

? ? }

? ? @Override

? ? public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

? ? ? ? ServletRequest requestWrapper = null;

? ? ? ? if (servletRequest instanceof HttpServletRequest) {

? ? ? ? ? ? String url = ((HttpServletRequest) servletRequest).getRequestURI();

? ? ? ? ? ? //判斷接口是否為導入接口

? ? ? ? ? ? if ("/resource/guide".equals(url)) {

? ? ? ? ? ? ? ? Part file = ((HttpServletRequest) servletRequest).getPart("file");

? ? ? ? ? ? ? ? log.info("文件名:" + file);

? ? ? ? ? ? }

? ? ? ? ? ? requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest);

? ? ? ? }

? ? ? ? if (requestWrapper == null) {

? ? ? ? ? ? filterChain.doFilter(servletRequest, servletResponse);

? ? ? ? }else{

? ? ? ? ? ? filterChain.doFilter(requestWrapper, servletResponse);

? ? ? ? }

? ? }

? ? @Override

? ? public void destroy() {

? ? }

}

```

#### handle層

![UTOOLS1588256874063.png](https://johnnycc.oss-cn-beijing.aliyuncs.com/UTOOLS1588256874063.png)

###### GlobalExceptionHandle

定義全局異常

```

/**

* @author Johnny

* @Date: 2020/4/22 10:40

* @Description:

*/

@RestControllerAdvice(annotations = {RestController.class, Controller.class})

@Slf4j

public class GlobalExceptionHandle {

? ? //自定義異常

? ? /**

? ? * JwtException

? ? * @param exception

? ? * @return

? ? */

? ? @ExceptionHandler(value = {JwtException.class})

? ? @ResponseBody

? ? public ResponseResult sendError(JwtException exception) {

? ? ? ? log.error(exception.getMessage());

? ? ? ? return ResponseResult.failure(exception.getResultCode());

? ? }

? ? /**

? ? * CustomException

? ? * @param exception

? ? * @return

? ? */

? ? @ExceptionHandler(value = {CustomException.class})

? ? @ResponseBody

? ? public ResponseResult sendError(CustomException exception) {

? ? ? ? log.error(exception.getMessage());

? ? ? ? return ResponseResult.failure(exception.getResultCode());

? ? }

? ? //系統級異常

? ? /**

? ? * InvalidClassException

? ? * @param exception

? ? * @return

? ? */

? ? @ExceptionHandler(value = {InvalidClassException.class})

? ? @ResponseBody

? ? public ResponseResult sendError(InvalidClaimException exception) {

? ? ? ? log.error(exception.getMessage());

? ? ? ? //返回token已經過期

? ? ? ? return ResponseResult.failure(ResultCode.USER_TOKEN_EXPIRES);

? ? }

? ? /**

? ? * NullPointerException

? ? * @param exception

? ? * @return

? ? */

? ? @ExceptionHandler(value = {NullPointerException.class})

? ? @ResponseBody

? ? public ResponseResult sendError(NullPointerException exception) {

? ? ? ? log.error(exception.getMessage());

? ? ? ? //空指針提示數據未找到

? ? ? ? return ResponseResult.failure(ResultCode.RESULT_CODE_DATA_NONE);

? ? }

? ? /**

? ? * IOException

? ? * @param exception

? ? * @return

? ? */

? ? @ExceptionHandler(value = {IOException.class})

? ? @ResponseBody

? ? public ResponseResult sendError(IOException exception) {

? ? ? ? log.error(exception.getMessage());

? ? ? ? //io異常提示驗證碼出現問題

? ? ? ? return ResponseResult.failure(ResultCode.CAPTCHA_ERROR);

? ? }

```

###### GlobalResponseHandle

處理全局的響應頭(當接口返回類型非ResponseResult類型時,可以自動轉換)

```

/**

* @author Johnny

* @Date: 2020/4/22 10:55

* @Description:

*/

@ControllerAdvice

public class GlobalResponseHandle implements ResponseBodyAdvice {

? ? /**

? ? * 處理響應的具體方法

? ? * @param returnType

? ? * @param converterType

? ? * @return

? ? */

? ? @Override

? ? public boolean supports(MethodParameter returnType, Class converterType) {

? ? ? ? return true;

? ? }

? ? @Override

? ? public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {

? ? ? ? if (body instanceof ResponseResult) {

? ? ? ? ? ? return body;

? ? ? ? }else{

? ? ? ? ? ? return ResponseResult.success(body);

? ? ? ? }

? ? }

}

```

#### interceptor

![image-20200430223215350](C:\Users\Jack\AppData\Roaming\Typora\typora-user-images\image-20200430223215350.png)

###### JwtInterceptor

```

/**

* @author Johnny

* @Date: 2020/4/23 19:41

* @Description:

*/

@Slf4j

@Component

public class JwtInterceptor? implements HandlerInterceptor {

? ? @Resource

? ? SysRoleService roleService;

? ? @Resource

? ? private RedisService redisService;

? ? @Override

? ? public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

? ? ? ? String token = request.getHeader("Authorization");

? ? ? ? log.info("token———————————"+token);

? ? ? ? //認證

? ? ? ? if (token == null) {

? ? ? ? ? ? log.info("用戶未登錄");

? ? ? ? ? ? return false;

? ? ? ? }else{

? ? ? ? ? ? //登錄之后

? ? ? ? ? ? //從請求頭當中取出用戶id

? ? ? ? ? ? String adminId = request.getHeader("id");

? ? ? ? ? ? log.info("用戶id" + adminId);

? ? ? ? ? ? //在redis中檢查是否存在adminId為key的數據

? ? ? ? ? ? if (!redisService.existsKey(adminId)) {

? ? ? ? ? ? ? ? log.info("redis中不存在此鍵");

? ? ? ? ? ? }else{

? ? ? ? ? ? ? ? String secrect = redisService.getValue(adminId, String.class);

? ? ? ? ? ? ? ? //從token中解析出roles字符串

? ? ? ? ? ? ? ? String roles = JwtTokenUtil.getRoles(token,secrect);

? ? ? ? ? ? ? ? //反序列化成List

? ? ? ? ? ? ? ? List<SysRole> sysRoles = JSONArray.parseArray(roles, SysRole.class);

? ? ? ? ? ? ? ? //從request中取得客戶端傳輸的roleId

? ? ? ? ? ? ? ? String roleId = request.getParameter("roleId");

? ? ? ? ? ? ? ? log.info("roleId:"+roleId);

? ? ? ? ? ? ? ? boolean flag = roleService.checkRole(sysRoles,Integer.parseInt(roleId));

? ? ? ? ? ? ? ? if(flag){

? ? ? ? ? ? ? ? ? ? return true;

? ? ? ? ? ? ? ? }else{

? ? ? ? ? ? ? ? ? log.info("用戶不具備此角色");

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? ? ? return false;

? ? ? ? }

? ? }

}

```

###### LoginInterceptor

```

/**

* @author Johnny

* @Date: 2020/4/21 21:00

* @Description:

*/

@Slf4j

@Component

public class LoginInterceptor implements HandlerInterceptor {

? ? @Resource

? ? private RedisService redisService;

? ? @Override

? ? public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handle) {

? ? ? ? //將request包裝成HttpServletWrapper類型

? ? ? ? RequestWrapper requestWrapper = new RequestWrapper(request);

? ? ? ? //取得請求的json對象

? ? ? ? String body = requestWrapper.getBody();

? ? ? ? log.info(body);

? ? ? ? //從redis中取得指定用戶名的驗證碼

? ? ? ? JSONObject jsonObject = JSONObject.parseObject(body);

? ? ? ? String name = jsonObject.getString("name");

? ? ? ? System.out.println(name);

? ? ? ? String password = jsonObject.getString("password");

? ? ? ? System.out.println(password);

? ? ? ? String verifyCode = jsonObject.getString("verifyCode");

? ? ? ? System.out.println(verifyCode);

? ? ? ? LoginDto loginDto = LoginDto.builder().name(name).password(password).verifyCode(verifyCode).build();

? ? ? ? return true;

//? ? ? ? if (redisService.existsKey(loginDto.getName())) {

//? ? ? ? ? ? //獲取redis中的驗證碼

//? ? ? ? ? ? String correctCode = redisService.getValue(name, String.class);

//? ? ? ? ? ? //忽略大小寫,成功則放行到controller調用登錄接口

//? ? ? ? ? ? if (correctCode.equalsIgnoreCase(verifyCode)) {

//? ? ? ? ? ? ? ? return true;

//? ? ? ? ? ? }else{

//? ? ? ? ? ? ? ? log.info("驗證碼錯誤");

//? ? ? ? ? ? ? ? return false;

//? ? ? ? ? ? }

//? ? ? ? }else{

//? ? ? ? ? ? log.info("驗證碼失效");

//? ? ? ? ? ? return false;

//? ? ? ? }

? ? }

}

```

#### mapper層

這個目前不做過多贅述,我會盡快整理下mybatisplus的常用語法發出來

#### util層

![UTOOLS1588257313481.png](https://johnnycc.oss-cn-beijing.aliyuncs.com/UTOOLS1588257313481.png)

###### ExcelConsumer

```

/**

* @author Johnny

* @Date: 2020/4/28 21:53

* @Description:

*/

@Slf4j

public class ExcelConsumer<T>? implements Runnable {

? ? /**

? ? * 一張工作表可以容納的最大行數

? ? */

? ? private static Integer SHEET_SIZE = 100000;

? ? /**

? ? * 導出的Vo數據類型

? ? */

? ? private Class<T> clazz;

? ? /**

? ? * 工作簿

? ? */

? ? private SXSSFWorkbook wb;

? ? /**

? ? * 工作表名稱

? ? */

? ? private String sheetName;

? ? /**

? ? * 數據緩沖區對象

? ? */

? ? private ExportDataAdapter<T> exportDataAdapter;

? ? /**

? ? * 線程同步

? ? */

? ? private CountDownLatch latch;

? ? /**

? ? * 構造方法

? ? *

? ? * @param clazz

? ? */

? ? public ExcelConsumer(Class<T> clazz, ExportDataAdapter<T> exportDataAdapter, SXSSFWorkbook wb, CountDownLatch latch, String sheetName) {

? ? ? ? if (clazz == null || wb == null || exportDataAdapter == null || latch == null) {

? ? ? ? ? ? log.error("ExcelConsumer::初始化對象參數不能為空");

? ? ? ? ? ? return;

? ? ? ? }

? ? ? ? this.clazz = clazz;

? ? ? ? this.exportDataAdapter = exportDataAdapter;

? ? ? ? this.wb = wb;

? ? ? ? this.latch = latch;

? ? ? ? this.sheetName = sheetName == null ? "UnNamedSheet" : sheetName;

? ? }

? ? @Override

? ? public void run() {

? ? ? ? //初始化excel導出工具類

? ? ? ? ExcelUtil<T> excelUtil = new ExcelUtil<>(this.clazz);

? ? ? ? Sheet sheet = null;

? ? ? ? int sheetNo = 0;

? ? ? ? int rowNum = 1;

? ? ? ? T vo;

? ? ? ? //生產者還在生產數據

? ? ? ? while (latch.getCount() > 1) {

? ? ? ? ? ? //生成sheetName

? ? ? ? ? ? if (rowNum == 1) {

? ? ? ? ? ? ? ? sheetNo++;

? ? ? ? ? ? ? ? sheet = excelUtil.createSheet(wb, sheetName.concat(Integer.toString(sheetNo)));

? ? ? ? ? ? ? ? excelUtil.setColumnTitle(sheet);

? ? ? ? ? ? }

? ? ? ? ? ? //獲取數據

? ? ? ? ? ? vo = exportDataAdapter.getData();

? ? ? ? ? ? //往excel添加一行數據

? ? ? ? ? ? excelUtil.addRowData(vo, wb, sheet, rowNum);

? ? ? ? ? ? rowNum++;

? ? ? ? ? ? //準備生成下一個sheetName

? ? ? ? ? ? if (rowNum == SHEET_SIZE + 1) {

? ? ? ? ? ? ? ? rowNum = 1;

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? //生產者不再生產數據,取剩余數據,并將數據寫入excel

? ? ? ? Integer reminderDataSize = exportDataAdapter.getDataSize();

? ? ? ? T reminderData;

? ? ? ? if (reminderDataSize > 0) {

? ? ? ? ? ? for (int i = 0; i < reminderDataSize; i++) {

? ? ? ? ? ? ? ? reminderData = exportDataAdapter.getData();

? ? ? ? ? ? ? ? if (rowNum == 1) {

? ? ? ? ? ? ? ? ? ? sheetNo++;

? ? ? ? ? ? ? ? ? ? sheet = excelUtil.createSheet(wb, sheetName.concat(Integer.toString(sheetNo)));

? ? ? ? ? ? ? ? ? ? excelUtil.setColumnTitle(sheet);

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? excelUtil.addRowData(reminderData, wb, sheet, rowNum);

? ? ? ? ? ? ? ? rowNum++;

? ? ? ? ? ? ? ? if (rowNum == SHEET_SIZE + 1) {

? ? ? ? ? ? ? ? ? ? rowNum = 1;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? log.info("數據導出完成");

? ? ? ? latch.countDown();

? ? }

}

```

###### ExcelUtil

```

/**

* @author Johnny

* @Date: 2020/4/28 21:47

* @Description:

*/

public class ExcelUtil<T> {

? ? Class<T> clazz;

? ? /**

? ? * 表頭字段列表

? ? */

? ? private List<Field> fields;

? ? /**

? ? * 數字單元格對象

? ? */

? ? private CellStyle decimalCellStyle = null;

? ? /**

? ? * 日期時間單元格對象

? ? */

? ? private CellStyle dateTimeCellStyle = null;

? ? /**

? ? * 構造方法

? ? *

? ? * @param clazz

? ? */

? ? public ExcelUtil(Class<T> clazz) {

? ? ? ? this.clazz = clazz;

? ? }

? ? /**

? ? * 添加一條數據

? ? *

? ? * @param vo:需要導出的vo對象

? ? * @param wb:工作簿

? ? * @param sheet:工作表

? ? * @param rowNum:當前行號

? ? */

? ? public void addRowData(T vo, SXSSFWorkbook wb, Sheet sheet, int rowNum) {

? ? ? ? //創建一行

? ? ? ? Row row = sheet.createRow(rowNum);

? ? ? ? Field field;

? ? ? ? Cell cell;

? ? ? ? ExcelVoAttribute attr;

? ? ? ? int fieldSize = fields.size();

? ? ? ? // 遍歷入參對象的所有屬性

? ? ? ? for (int j = 0; j < fieldSize; j++) {

? ? ? ? ? ? // 通過反射獲得需要導出的入參對象的所有屬性

? ? ? ? ? ? field = fields.get(j);

? ? ? ? ? ? // 設置實體類私有屬性可訪問

? ? ? ? ? ? field.setAccessible(true);

? ? ? ? ? ? // 獲取所有添加了注解的屬性

? ? ? ? ? ? attr = field.getAnnotation(ExcelVoAttribute.class);

? ? ? ? ? ? // 給每個屬性創建一個單元格

? ? ? ? ? ? cell = row.createCell(j);

? ? ? ? ? ? try {

? ? ? ? ? ? ? ? this.setCellValue(attr, field.get(vo), wb, cell);

? ? ? ? ? ? } catch (IllegalAccessException e) {

? ? ? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? }

? ? ? ? }

? ? }

? ? /**

? ? * 根據注解類判斷字段類型設置excel單元格數據格式方法

? ? *

? ? * @param attr

? ? * @param valueObject

? ? * @param workbook

? ? * @param cell

? ? */

? ? private void setCellValue(ExcelVoAttribute attr, Object valueObject, SXSSFWorkbook workbook, Cell cell) {

? ? ? ? String returnValue;

? ? ? ? if (attr.isNumber()) {

? ? ? ? ? ? cell.setCellStyle(getCellStyle(attr, workbook));

? ? ? ? ? ? returnValue = valueObject == null || "".equals(valueObject) ? "0" : valueObject.toString();

? ? ? ? ? ? BigDecimal num = new BigDecimal(returnValue);

? ? ? ? ? ? cell.setCellValue(num.doubleValue());

? ? ? ? } else if (attr.isDateTime()) {

? ? ? ? ? ? cell.setCellStyle(getCellStyle(attr, workbook));

? ? ? ? ? ? DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

? ? ? ? ? ? returnValue = df.format((TemporalAccessor) valueObject);

? ? ? ? ? ? cell.setCellValue(returnValue);

? ? ? ? } else {

? ? ? ? ? ? returnValue = valueObject == null ? "" : valueObject.toString();

? ? ? ? ? ? cell.setCellValue(returnValue);

? ? ? ? }

? ? }

? ? /**

? ? * 根據注解類判斷字段類型,返回excel單元格數據格式方法

? ? *

? ? * @param attr

? ? * @param workbook

? ? * @return CellStyle

? ? */

? ? private CellStyle getCellStyle(ExcelVoAttribute attr, SXSSFWorkbook workbook) {

? ? ? ? if (attr.isNumber()) {

? ? ? ? ? ? if (decimalCellStyle == null) {

? ? ? ? ? ? ? ? decimalCellStyle = workbook.createCellStyle();

? ? ? ? ? ? ? ? //此處設置數字單元格格式

? ? ? ? ? ? ? ? DataFormat df = workbook.createDataFormat();

? ? ? ? ? ? ? ? //千分位,保留1位小數

? ? ? ? ? ? ? ? decimalCellStyle.setDataFormat(df.getFormat("#,##0.0"));

? ? ? ? ? ? }

? ? ? ? ? ? return decimalCellStyle;

? ? ? ? }

? ? ? ? if (attr.isDateTime()) {

? ? ? ? ? ? if (dateTimeCellStyle == null) {

? ? ? ? ? ? ? ? dateTimeCellStyle = workbook.createCellStyle();

? ? ? ? ? ? ? ? //此處設置日期時間單元格格式

? ? ? ? ? ? ? ? DataFormat df = workbook.createDataFormat();

? ? ? ? ? ? ? ? dateTimeCellStyle.setDataFormat(df.getFormat("yyyy-MM-dd HH:mm:ss"));

? ? ? ? ? ? }

? ? ? ? ? ? return dateTimeCellStyle;

? ? ? ? }

? ? ? ? return null;

? ? }

? ? /**

? ? * 創建工作頁Sheet

? ? *

? ? * @param wb

? ? * @param sheetName

? ? * @return Sheet

? ? */

? ? public Sheet createSheet(SXSSFWorkbook wb, String sheetName) {

? ? ? ? return wb.createSheet(sheetName);

? ? }

? ? /**

? ? * 設置excel列頭及格式

? ? *

? ? * @param sheet

? ? */

? ? public void setColumnTitle(Sheet sheet) {

? ? ? ? if (fields == null) {

? ? ? ? ? ? this.fields = this.getSortFields();

? ? ? ? }

? ? ? ? Row row;

? ? ? ? Cell cell;

? ? ? ? ExcelVoAttribute attr;

? ? ? ? Field field;

? ? ? ? int fieldSize = fields.size();

? ? ? ? row = sheet.createRow(0);

? ? ? ? for (int i = 0; i < fieldSize; i++) {

? ? ? ? ? ? field = fields.get(i);

? ? ? ? ? ? attr = field.getAnnotation(ExcelVoAttribute.class);

? ? ? ? ? ? cell = CellUtil.createCell(row, i, attr.name());

? ? ? ? ? ? // 設置列寬,根據相應的字段名的長度等比

? ? ? ? ? ? sheet.setColumnWidth(i, attr.name().getBytes().length * 400);

? ? ? ? }

? ? }

? ? /**

? ? * 獲取輸出對象字段列表,并根據注解進行字段排序

? ? *

? ? * @return

? ? */

? ? private List<Field> getSortFields() {

? ? ? ? List<Field> fields = Arrays.stream(clazz.getDeclaredFields()).filter(x -> x.isAnnotationPresent(ExcelVoAttribute.class)).collect(Collectors.toList());

? ? ? ? List<Field> sortList = new ArrayList<>(fields);

? ? ? ? //排序

? ? ? ? for (Field field : fields) {

? ? ? ? ? ? ExcelVoAttribute excelVoAttribute = field.getAnnotation(ExcelVoAttribute.class);

? ? ? ? ? ? int sortNo = excelVoAttribute.column();

? ? ? ? ? ? sortList.set(sortNo, field);

? ? ? ? }

? ? ? ? return sortList;

? ? }

}

```

###### ExcelUtils

```

/**

* @author Johnny

* @Date: 2020/4/25 22:56

* @Description:

*/

@Slf4j

public class ExcelUtils {

? ? /**

? ? * 導出歌曲

? ? * @param response

? ? * @param list

? ? * @param map

? ? * @param title

? ? */

? ? public static void exportExcel(HttpServletResponse response, List list, Map<String, String> map, String title) {

? ? ? ? //通過工具類創建writer

? ? ? ? ExcelWriter writer = ExcelUtil.getWriter(true);

? ? ? ? //自定義標題別名

? ? ? ? Set<Map.Entry<String, String>> entries = map.entrySet();

? ? ? ? //迭代器遍歷數據

? ? ? ? Iterator<Map.Entry<String, String>> iterator = entries.iterator();

? ? ? ? while (iterator.hasNext()) {

? ? ? ? ? ? Map.Entry<String, String> next = iterator.next();

? ? ? ? ? ? //自定義表頭

? ? ? ? ? ? writer.addHeaderAlias(next.getKey(), next.getValue());

? ? ? ? }

? ? ? ? //合并單元格后的標題行,使用默認標題樣式

? ? ? ? writer.merge(map.size() - 1, title);

? ? ? ? //一次性寫出內容,使用默認樣式,強制輸出標題

? ? ? ? writer.write(list, true);

? ? ? ? //out為outputStream,需要寫出到的目標流

? ? ? ? try {

? ? ? ? ? ? writer.flush(response.getOutputStream(), true);

? ? ? ? } catch (IOException e) {

? ? ? ? ? ? log.info("歌單導出異常");

? ? ? ? ? ? e.printStackTrace();

? ? ? ? }

? ? ? ? writer.close();

? ? }

? ? /**

? ? * 導入歌曲

? ? * @param file

? ? * @return

? ? */

? ? public static List<Song> importExcel(File file) {

? ? ? ? List<Song> songs = new ArrayList<>();

? ? ? ? InputStream inputStream = null;

? ? ? ? try {

? ? ? ? ? ? inputStream = new FileInputStream(file);

? ? ? ? } catch (FileNotFoundException e) {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? }

? ? ? ? //通過getReader方法確定讀取那個sheet中的數據

? ? ? ? ExcelReader reader = ExcelUtil.getReader(inputStream, "sheet1");

? ? ? ? //回去總行數

? ? ? ? List<List<Object>> read = reader.read(1, reader.getRowCount());

? ? ? ? for (List<Object> objects : read) {

? ? ? ? ? ? //對每行數據取出構建一個song對象

? ? ? ? ? ? Song song = Song.builder()

? ? ? ? ? ? ? ? ? ? .songName(objects.get(0).toString())

? ? ? ? ? ? ? ? ? ? .songId(UUID.randomUUID().toString().replace("-", ""))

? ? ? ? ? ? ? ? ? ? .sortId("0")

? ? ? ? ? ? ? ? ? ? .singer(objects.get(1).toString())

? ? ? ? ? ? ? ? ? ? .duration(objects.get(2).toString())

? ? ? ? ? ? ? ? ? ? .thumbnail(objects.get(3).toString())

? ? ? ? ? ? ? ? ? ? .url(objects.get(4).toString())

? ? ? ? ? ? ? ? ? ? .lyric(objects.get(5).toString())

? ? ? ? ? ? ? ? ? ? .commentCount(0)

? ? ? ? ? ? ? ? ? ? .playCount(0)

? ? ? ? ? ? ? ? ? ? .deleteFlag("0")

? ? ? ? ? ? ? ? ? ? .updateTime(LocalDateTime.now())

? ? ? ? ? ? ? ? ? ? .createTime(LocalDateTime.now())

? ? ? ? ? ? ? ? ? ? .build();

? ? ? ? ? ? songs.add(song);

? ? ? ? }

? ? ? ? return songs;

? ? }

}

```

###### ExportDataAdapter

```

/**

* @author Johnny

* @Date: 2020/4/28 21:53

* @Description:

*/

public class ExportDataAdapter<T>{

? ? /**

? ? * 默認隊列大小

? ? */

? ? private static Integer DEFAULT_SIZE = 1000;

? ? private BlockingQueue<T> resourceQueue = null;

? ? public ExportDataAdapter() {

? ? ? ? this.resourceQueue = new LinkedBlockingQueue<T>(DEFAULT_SIZE);

? ? }

? ? /**

? ? * 添加數據

? ? *

? ? * @param data

? ? */

? ? public void addData(T data) {

? ? ? ? try {

? ? ? ? ? ? resourceQueue.put(data);

? ? ? ? } catch (InterruptedException e) {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? }

? ? }

? ? /**

? ? * 獲取剩余數據數量

? ? *

? ? * @return

? ? */

? ? public Integer getDataSize() {

? ? ? ? return resourceQueue.size();

? ? }

? ? /**

? ? * 從隊列中獲取數據

? ? *

? ? * @return

? ? */

? ? public T getData() {

? ? ? ? try {

? ? ? ? ? ? return resourceQueue.take();

? ? ? ? } catch (InterruptedException e) {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? }

? ? ? ? return null;

? ? }

}

```

###### FileUtil

```

/**

* @author Johnny

* @Date: 2020/4/26 10:41

* @Description:

*/

public class FileUtil {

? ? public static File fileConversion(MultipartFile file) {

? ? ? ? int n;

? ? ? ? File file1 = new File(file.getOriginalFilename());

? ? ? ? try {

? ? ? ? ? ? InputStream in = file.getInputStream();

? ? ? ? ? ? OutputStream os = new FileOutputStream(file1);

? ? ? ? ? ? byte[] bytes = new byte[4096];

? ? ? ? ? ? while ((n=in.read(bytes,0,4096))!=-1){

? ? ? ? ? ? ? ? os.write(bytes,0,n);

? ? ? ? ? ? ? ? File file2 = new File(file1.toURI());

? ? ? ? ? ? ? ? file2.delete();

? ? ? ? ? ? }

? ? ? ? } catch (IOException e) {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? }

? ? ? ? return file1;

? ? }

}

```

###### JwtTokenUtil

```

/**

* @Author Johnny

* @Date 2020/4/15

* @Version 1.0

*/

@Slf4j

public class JwtTokenUtil {

? ? /**

? ? * 加密

? ? *

? ? * @param userId

? ? * @param expiresAt

? ? * @return String

? ? */

? ? public static String getToken(final String userId, String userRole,final String secrect,Date expiresAt) {

? ? ? ? String token = null;

? ? ? ? try {

? ? ? ? ? ? token = JWT.create()

? ? ? ? ? ? ? ? ? ? .withIssuer("auth0")

? ? ? ? ? ? ? ? ? ? .withClaim("userId", userId)

? ? ? ? ? ? ? ? ? ? .withClaim("userRole", userRole)

? ? ? ? ? ? ? ? ? ? .withExpiresAt(expiresAt)

? ? ? ? ? ? ? ? ? ? // 使用了HMAC256加密算法, mySecret是用來加密數字簽名的密鑰

? ? ? ? ? ? ? ? ? ? .sign(Algorithm.HMAC256(secrect));

? ? ? ? } catch (UnsupportedEncodingException e) {

? ? ? ? ? ? log.error("不支持的編碼格式");

? ? ? ? }

? ? ? ? return token;

? ? }

? ? /**

? ? * 解密

? ? * @param token

? ? * @return DecodedJWT

? ? */

? ? public static DecodedJWT deToken(final String token,final String secrect) {

? ? ? ? DecodedJWT jwt;

? ? ? ? JWTVerifier verifier = null;

? ? ? ? try {

? ? ? ? ? ? verifier = JWT.require(Algorithm.HMAC256(secrect))

? ? ? ? ? ? ? ? ? ? .withIssuer("auth0")

? ? ? ? ? ? ? ? ? ? .build();

? ? ? ? } catch (UnsupportedEncodingException e) {

? ? ? ? ? ? log.error("不支持的編碼格式");

? ? ? ? }

? ? ? ? assert verifier != null;

? ? ? ? jwt = verifier.verify(token);

? ? ? ? return jwt;

? ? }

? ? /**

? ? * 獲取userId

? ? *

? ? * @param token

? ? * @return String

? ? */

? ? public static String getUserId(String token,String secrect) {

? ? ? ? return deToken(token,secrect).getClaim("userId").asString();

? ? }

? ? /**

? ? * 獲取role

? ? *

? ? * @param token

? ? * @return String

? ? */

? ? public static String getRoles(String token,String secrect) {

? ? ? ? return deToken(token,secrect).getClaim("userRole").asString();

? ? }

? ? /**

? ? * 驗證是否過期

? ? *

? ? * @param token

? ? * @return boolean

? ? */

? ? public static boolean isExpiration(String token,String secrect) {

? ? ? ? return deToken(token,secrect).getExpiresAt().before(new Date());

? ? }

? ? public static void main(String[] args) {

//? ? ? ? String token = getToken("2000100193", new Date(System.currentTimeMillis() + 10L * 1000L));

//? ? ? ? System.out.println(token);

//? ? ? ? while (true) {

//? ? ? ? ? ? boolean flag = isExpiration(token);

//? ? ? ? ? ? System.out.println(flag);

//? ? ? ? ? ? if (flag) {

//? ? ? ? ? ? ? ? System.out.println("token已失效");

//? ? ? ? ? ? ? ? break;

//? ? ? ? ? ? }

//? ? ? ? ? ? try {

//? ? ? ? ? ? ? ? Thread.sleep(1000);

//? ? ? ? ? ? } catch (InterruptedException e) {

//? ? ? ? ? ? ? ? e.printStackTrace();

//? ? ? ? ? ? }

//? ? ? ? }

//? ? ? ? String token = "eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJhdXRoMCIsImV4cCI6MTU4NzY3MTQ3OCwidXNlcklkIjoiREUzNUQ3Q0MwNUFGOTZBMjFEN0FERkM4NjUxRTY2MTQifQ.uT2W3QcE3744WNN3inEKT8lUVJs6xAC7TodDCaWkcyM";

//? ? ? ? System.out.println(deToken(token).getClaim("userId").asString());

? ? }

}

```

###### Md5Util

```

/**

* @author Johnny

*/

public class Md5Util {

? ? /**

? ? * @param pwd? ? 需要加密的字符串

? ? * @param isUpper 字母大小寫(false為默認小寫,true為大寫)

? ? * @param bit? ? 加密的位數(16,32,64)

? ? * @return String

? ? */

? ? public static String getMd5(String pwd, boolean isUpper, Integer bit) {

? ? ? ? String md5 = "";

? ? ? ? try {

? ? ? ? ? ? // 創建加密對象

? ? ? ? ? ? MessageDigest md = MessageDigest.getInstance("md5");

? ? ? ? ? ? if (bit == 64) {

? ? ? ? ? ? ? ? Base64.Encoder encoder = Base64.getEncoder();

? ? ? ? ? ? ? ? md5 = encoder.encodeToString(md.digest(pwd.getBytes(StandardCharsets.UTF_8)));

? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? // 計算MD5函數

? ? ? ? ? ? ? ? md.update(pwd.getBytes());

? ? ? ? ? ? ? ? byte b[] = md.digest();

? ? ? ? ? ? ? ? int i;

? ? ? ? ? ? ? ? StringBuilder sb = new StringBuilder();

? ? ? ? ? ? ? ? for (byte value : b) {

? ? ? ? ? ? ? ? ? ? i = value;

? ? ? ? ? ? ? ? ? ? if (i < 0) {

? ? ? ? ? ? ? ? ? ? ? ? i += 256;

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? if (i < 16) {

? ? ? ? ? ? ? ? ? ? ? ? sb.append("0");

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? sb.append(Integer.toHexString(i));

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? md5 = sb.toString();

? ? ? ? ? ? ? ? if (bit == 16) {

? ? ? ? ? ? ? ? ? ? //截取32位md5為16位

? ? ? ? ? ? ? ? ? ? md5 = md5.substring(8, 24).toString();

? ? ? ? ? ? ? ? ? ? if (isUpper) {

? ? ? ? ? ? ? ? ? ? ? ? md5 = md5.toUpperCase();

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? return md5;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? ? ? //轉換成大寫

? ? ? ? ? ? if (isUpper) {

? ? ? ? ? ? ? ? md5 = md5.toUpperCase();

? ? ? ? ? ? }

? ? ? ? } catch (Exception e) {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? System.out.println("md5加密拋出異常!");

? ? ? ? }

? ? ? ? return md5;

? ? }

? ? public static void main(String[] args) {

? ? ? ? String a = "123456";

? ? ? ? String md5a = getMd5(a, true, 32);

? ? ? ? System.out.println(md5a);

? ? ? ? System.out.println(md5a.length());

? ? }

```

###### ThreadPool

```

/**

* @author Johnny

* @Date: 2020/4/28 21:55

* @Description:

*/

public class ThreadPool {

? ? /**

? ? * 異步線程

? ? */

? ? private final static ThreadPoolExecutor executor =

? ? ? ? ? ? new ThreadPoolExecutor(20, 100, 10, TimeUnit.MINUTES,

? ? ? ? ? ? ? ? ? ? new ArrayBlockingQueue<>(2000),

? ? ? ? ? ? ? ? ? ? r -> new Thread(r, "excelExportThread"),

? ? ? ? ? ? ? ? ? ? new ThreadPoolExecutor.AbortPolicy());

? ? public static ThreadPoolExecutor getExecutor() {

? ? ? ? return executor;

? ? }

}

```

###### TreeBuilder

```

/**

* @author Johnny

* @Date: 2020/4/23 18:46

* @Description:

*/

public class TreeBuilder {

? ? /**

? ? * 兩層循環實現建樹

? ? *

? ? * @param treeNodes 傳入的樹節點列表

? ? * @return

? ? */

? ? public static List<TreeNode> buildTreeByLoop(List<TreeNode> treeNodes) {

? ? ? ? List<TreeNode> trees = new ArrayList<>();

? ? ? ? for (TreeNode treeNode : treeNodes) {

? ? ? ? ? ? if (treeNode.getParentId() == 0) {

? ? ? ? ? ? ? ? trees.add(treeNode);

? ? ? ? ? ? }

? ? ? ? ? ? for (TreeNode it : treeNodes) {

? ? ? ? ? ? ? ? if (it.getParentId().equals(treeNode.getId())) {

? ? ? ? ? ? ? ? ? ? if (treeNode.getSubMenus() == null) {

? ? ? ? ? ? ? ? ? ? ? ? treeNode.setSubMenus(new ArrayList<>());

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? treeNode.getSubMenus().add(it);

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? return trees;

? ? }

? ? /**

? ? * 使用遞歸方法建樹

? ? *

? ? * @param treeNodes

? ? * @return

? ? */

? ? public static List<TreeNode> buildTreeByRecursive(List<TreeNode> treeNodes) {

? ? ? ? List<TreeNode> trees = new ArrayList<>();

? ? ? ? for (TreeNode treeNode : treeNodes) {

? ? ? ? ? ? if (treeNode.getParentId() == 0) {

? ? ? ? ? ? ? ? trees.add(findChildren(treeNode, treeNodes));

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? return trees;

? ? }

? ? /**

? ? * 遞歸查找子節點

? ? *

? ? * @param treeNodes

? ? * @return

? ? */

? ? public static TreeNode findChildren(TreeNode treeNode, List<TreeNode> treeNodes) {

? ? ? ? for (TreeNode it : treeNodes) {

? ? ? ? ? ? if (treeNode.getId().equals(it.getParentId())) {

? ? ? ? ? ? ? ? if (treeNode.getSubMenus() == null) {

? ? ? ? ? ? ? ? ? ? treeNode.setSubMenus(new ArrayList<>());

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? treeNode.getSubMenus().add(findChildren(it, treeNodes));

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? return treeNode;

? ? }

}

```

###### TreeNode

```

/**

* @author Johnny

* @Date: 2020/4/23 18:46

* @Description:

*/

@Data

@NoArgsConstructor

@AllArgsConstructor

@Builder

public class TreeNode {

? ? private Integer id;

? ? private Integer parentId;

? ? private Integer type;

? ? private String title;

? ? private String icon;

? ? private String path;

? ? private Integer sort;

? ? private List<TreeNode> subMenus;

? ? public TreeNode(Integer id, Integer parentId, Integer type,String title, String icon, String path, Integer sort) {

? ? ? ? this.id = id;

? ? ? ? this.parentId = parentId;

? ? ? ? this.type = type;

? ? ? ? this.title = title;

? ? ? ? this.icon = icon;

? ? ? ? this.path = path;

? ? ? ? this.sort = sort;

? ? }

}

```、

更多內容請關注Johnny博客

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,794評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,750評論 2 370