国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Java-代碼生成器的實現(xiàn)

這篇具有很好參考價值的文章主要介紹了Java-代碼生成器的實現(xiàn)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。


前言

最近看了一個開源的項目,jfinal-layui,然后這個項目里面有一個 代碼生成器 的功能

Java-代碼生成器的實現(xiàn)

之前雖然有用過代碼生成器,但是從來沒有看過相關(guān)的源碼,所以就研究了一下,個人感覺這個項目的代碼生成器還是蠻好的,能夠根據(jù)指定的數(shù)據(jù)庫和表生成前后端的代碼,不過這個項目的框架是 jfinal ,直接把這個代碼生成器相關(guān)的代碼拷到自己的項目當中是運行不起來,而且每個項目的結(jié)構(gòu)都存在一些特有的性質(zhì),很難找到一個拿來就能用的代碼生成器,介于這一點,我就根據(jù)自己項目的架構(gòu),利用 jfinal 框架造的輪子,寫了一個較為簡單代碼生成器,同時分享一下我是怎樣實現(xiàn)的。

倘如各位想研究下 jfinal-layui 這個項目,可以點擊 https://gitee.com/QinHaiSenLin/Jfinal-layui 這個鏈接去拉取這個項目的源碼。


一、概述

代碼生成器顧名思義就是一個生成代碼的軟件,在日常開發(fā)中我們可以使用代碼生成器來減少一些重復(fù)的開發(fā)工作,提高開放效率,像我們經(jīng)常使用的 mybatis 就有一個逆向工程,它能自動幫我們生產(chǎn)數(shù)據(jù)庫對應(yīng)的實體類,mapper 和 xml 文件等,其實就是一個代碼生成器。

那代碼生成器的原理是什么呢?

其實很簡單,我們要生成的文件肯定是得有一定的規(guī)律,然后我們根據(jù)這個規(guī)律去編寫一個統(tǒng)一的模板,按照這個模板去生成相應(yīng)的代碼。


二、手寫代碼


1. 簡要說明

以下內(nèi)容僅供參考,我提供的只是一種實現(xiàn)方案,可能并不是適用于你的項目,但是只要你理解了我是怎么實現(xiàn)的,你也可以編寫一個適合你自己項目的代碼生成器。

脫離實際場景的業(yè)務(wù)沒什么意義,前面我也說過,任何項目都有它的獨特性,我是根據(jù)自己的項目來編寫一個符合我要求的代碼生成器,所以我先說下我自己的這個項目簡單的一個架構(gòu)。

比如說我的這個項目,要實現(xiàn)一個完整的增刪改查的一套的接口,需要有以下幾個類的存在,其中紅色框框選的部分(實體類 User.java、mapper 文件 UserMapper.java 和 xml 文件 UserMapper.xml)都可以通過 mybatis 的逆向工程生成,而綠色框框選的部分是需要我手動去編寫的,所以我需要寫的代碼生成器是能夠幫我去生成綠色框框選部分的文件,比如: Controller Service、ServiceImp 、請求體 dto、相應(yīng)體 dto 以及 subclass 包下的轉(zhuǎn)換器。

Java-代碼生成器的實現(xiàn)

關(guān)于我這個項目具體的搭建可查看這篇博客:SpringBoot - 快速搭建

那我該如何去實現(xiàn)了,首先我建議你自己寫一個標準的增刪改查,然后對照你寫的東西,去寫一個模板,比如我寫了一個崗位的 Controller ,代碼如下:

package com.duojiala.mikeboot.controller;

import com.duojiala.mikeboot.domain.req.QueryPostListReq;
import com.duojiala.mikeboot.domain.req.SavePostReq;
import com.duojiala.mikeboot.domain.dto.ResponseBean;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
import java.util.List;
import com.duojiala.mikeboot.service.PostService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
 * 頂部導(dǎo)航 Controller
 * @author: mike
 * @date: 2023-04-26 17:03
 */
@Slf4j
@RestController
@RequestMapping(value = "/post")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@CrossOrigin(origins = "*", methods = {RequestMethod.POST, RequestMethod.GET})
public class PostController {

    private final PostService postService;

    @PostMapping(value = "/save")
    @ApiOperation(value = "保存")
    public ResponseBean save(@RequestBody @Validated SavePostReq req) {
        postService.save(req);
        return ResponseBean.success();
    }

    @PostMapping(value = "/update")
    @ApiOperation(value = "修改")
    public ResponseBean update(@RequestBody @Validated SavePostReq req) {
        postService.update(req);
        return ResponseBean.success();
    }

    @PostMapping(value = "/detail")
    @ApiOperation(value = "查詢詳情")
    public ResponseBean detail(@RequestParam(required = false) String id) {
        return ResponseBean.success(postService.detail(id));
    }

    @PostMapping(value = "/delete")
    @ApiOperation(value = "刪除")
    public ResponseBean delete(@RequestParam(required = false) String id) {
        postService.delete(id);
        return ResponseBean.success();
    }

    @PostMapping(value = "/batch-delete")
    @ApiOperation(value = "批量刪除")
    public ResponseBean batchDelete(@RequestParam(required = false) List<String> ids) {
        postService.batchDelete(ids);
        return ResponseBean.success();
    }

    @PostMapping(value = "/paginate-list")
    @ApiOperation(value = "分頁查詢列表")
    public ResponseBean paginateList(@RequestBody @Validated QueryPostListReq req) {
        return ResponseBean.success(postService.paginateList(req));
    }


}

所以我寫得模板文件 controller_template.jf 就是這樣的

package #(controllerPackagePath);

#if(isCRUD)
import com.duojiala.mikeboot.domain.req.#(QueryTargetListReq??);
import com.duojiala.mikeboot.domain.req.#(SaveTargetReq??);
import com.duojiala.mikeboot.domain.dto.ResponseBean;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
import java.util.List;
#end
import com.duojiala.mikeboot.service.#(ServiceName??);
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
 * #(controllerRemark??) Controller
 * @author: #(author?? 'JBolt-Generator')
 * @date: #date()
 */
@Slf4j
@RestController
@RequestMapping(value = "/#(pathValue??)")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@CrossOrigin(origins = "*", methods = {RequestMethod.POST, RequestMethod.GET})
public class #(ControllerName??) {

    private final #(ServiceName??) #(serviceName??);

#if(isCRUD)
    @PostMapping(value = "/save")
    @ApiOperation(value = "保存")
    public ResponseBean save(@RequestBody @Validated #(SaveTargetReq??) req) {
        #(serviceName??).save(req);
        return ResponseBean.success();
    }

    @PostMapping(value = "/update")
    @ApiOperation(value = "修改")
    public ResponseBean update(@RequestBody @Validated #(SaveTargetReq??) req) {
        #(serviceName??).update(req);
        return ResponseBean.success();
    }

    @PostMapping(value = "/detail")
    @ApiOperation(value = "查詢詳情")
    public ResponseBean detail(@RequestParam(required = false) String id) {
        return ResponseBean.success(#(serviceName??).detail(id));
    }

    @PostMapping(value = "/delete")
    @ApiOperation(value = "刪除")
    public ResponseBean delete(@RequestParam(required = false) String id) {
        #(serviceName??).delete(id);
        return ResponseBean.success();
    }

    @PostMapping(value = "/batch-delete")
    @ApiOperation(value = "批量刪除")
    public ResponseBean batchDelete(@RequestParam(required = false) List<String> ids) {
        #(serviceName??).batchDelete(ids);
        return ResponseBean.success();
    }

#if(needPaginate)
    @PostMapping(value = "/paginate-list")
    @ApiOperation(value = "分頁查詢列表")
    public ResponseBean paginateList(@RequestBody @Validated #(QueryTargetListReq??) req) {
        return ResponseBean.success(#(serviceName??).paginateList(req));
    }
#else
    @PostMapping(value = "/list")
    @ApiOperation(value = "查詢列表")
    public ResponseBean list(@RequestBody @Validated #(QueryTargetListReq??) req) {
        return ResponseBean.success(#(serviceName??).list(req));
    }
#end
#end


}

這是通過自己項目中常寫的代碼,然后將公用的地方保留起來,結(jié)合一些模板引擎的語法將有差異的地方替換掉就行了,這里暫時看不懂也沒關(guān)系(模板引擎的語法在寫這個代碼生成器時我也不懂,不過看別人寫的對比下還是能寫出來的)

最終效果


2. 代碼編寫

因為是借了 jfinal 的輪子,所以這里我有引用到 jfinal 的一個依賴

        <!--JFianl-->
        <dependency>
            <groupId>com.jfinal</groupId>
            <artifactId>jfinal</artifactId>
            <version>5.0.4</version>
        </dependency>

還有就是一些工具類(這些根據(jù)你自己項目來選擇)

        <!-- 轉(zhuǎn)化器自動轉(zhuǎn)換 -->
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>1.4.2.Final</version>
        </dependency>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-processor</artifactId>
            <version>1.4.2.Final</version>
        </dependency>

        <!-- 分頁插件 -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.12</version>
        </dependency>

        <!--工具類-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.18</version>
        </dependency>

然后我還寫了一個枚舉類,希望每插入一個表,就能往這個枚舉類中添加一個該表的枚舉

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * 表名枚舉
 */
@Getter
@AllArgsConstructor
public enum TableEnum {
    USER("用戶表"),
    ;

    private final String desc;
}

建一個包用于存放代碼生成器 LogicGenerator.java,我先來給大家看一下生成 Controller 層的方法是如何實現(xiàn)的吧

    /**
     * 生成 Controller 層的代碼
     */
    public void genController(LogicBean logicBean) {
        // 獲取實體類名稱
        String entityName = logicBean.getEntityClass().getSimpleName();
        // 生成 Controller 類的名稱
        String controllerName = entityName + "Controller";
        // 生成 Controller 類的完整名稱
        String controllerFullName = controllerName + ".java";
        // 獲取 Controller 類的路徑
        String controllerPackagePath = logicBean.getControllerPackagePath();
        this.printMessageWithDate(String.format("正在生成 Controller:%s...", controllerFullName));
        // 獲取 Controller 生成路徑
        String targetOutputDir = this.getDirFromPackage(controllerPackagePath);
        this.printMessageWithDate(String.format("Controller 生成路徑:%s...", targetOutputDir));
        // 判斷將要生成的 Controller.java 是否存在
        if (FileUtil.exist(targetOutputDir + File.separator + controllerFullName)) {
            this.printMessageWithDate(String.format("Controller 文件[%s]已存在,忽略生成 ~~ ", controllerFullName));
        } else {
            String serviceName = entityName + "Service";
            // 替換 .jf 文件中的內(nèi)容,比如在 #(author??) 會替換成 logicBean.getAuthor() 這個值
            Kv<String,Object> data = Kv.by("author", (Object)logicBean.getAuthor())
                    .set("controllerPackagePath", controllerPackagePath)
                    .set("QueryTargetListReq", "Query"+entityName+"ListReq")
                    .set("SaveTargetReq", "Save"+entityName+"Req")
                    .set("ServiceName", serviceName)
                    .set("controllerRemark", logicBean.getTableEnumDes())
                    .set("pathValue", StringUtils.isBlank(logicBean.getPathValue())?toFirstLower(entityName):logicBean.getPathValue())
                    .set("ControllerName", controllerName)
                    .set("serviceName", toFirstLower(serviceName))
                    .set("needPaginate", logicBean.isNeedPaginate())
                    .set("isCRUD",logicBean.isCrudType());
            // 配置好模板路徑,通過 engine 去生成相應(yīng)的內(nèi)容
            String content = this.engine.getTemplate(controllerTemplate).renderToString(data);
            // IO 寫文件
            this.writeToFile(targetOutputDir, controllerFullName, content);
        }
    }

再寫一個帶有 main 方法的類,作為啟動生成器的入口

public class MainLogicGenerator extends LogicGenerator {
    public static void main(String[] args) {
        // 啟動代碼生成器
    }
}    

3. 完整代碼

LogicGenerator.java 代碼如下:

package com.duojiala.mikeboot.extend.gen;

import cn.hutool.core.io.FileUtil;
import com.duojiala.mikeboot.utils.DateUtil;
import com.jfinal.kit.StrKit;
import com.jfinal.template.Engine;
import org.apache.commons.lang3.StringUtils;

import javax.persistence.Table;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.*;

/**
 * 代碼生成器主類
 */
public class LogicGenerator {

    // 分隔符
    public final String SEPARATOR;
    // 項目路徑
    private final String PROJECT_PATH;
    // 枚舉類路徑
    private final String PROJECT_TABLE_ENUM_PATH;
    // Controller 層模板
    private static final String controllerTemplate = "/genera/controller_template.jf";
    // Service 模板
    private static final String serviceTemplate = "/genera/service_template.jf";
    // ServiceImpl 模板
    private static final String serviceImplTemplate = "/genera/service_impl_template.jf";
    // 轉(zhuǎn)換器模板
    private static final String converterTemplate = "/genera/converter_template.jf";
    // 通用 dto 模板
    private static final String commonDtoTemplate = "/genera/common_dto_template.jf";
    // 查詢 dto 模板
    private static final String queryDtoTemplate = "/genera/query_dto_template.jf";
    // 生成類隊列
    private final List<LogicBean> logicBeans;
    // jfinal 處理 jf 文件的引擎
    private Engine engine;

    /**
     * 初始化
     */
    public LogicGenerator() {
        this.SEPARATOR = File.separator;
        this.PROJECT_PATH = System.getProperty("user.dir");
        this.PROJECT_TABLE_ENUM_PATH = this.PROJECT_PATH + this.SEPARATOR + "src" + this.SEPARATOR + "main" + this.SEPARATOR + "java" + this.SEPARATOR + "com" + this.SEPARATOR + "duojiala" + this.SEPARATOR + "mikeboot" + this.SEPARATOR + "extend" + this.SEPARATOR + "system" + this.SEPARATOR + "TableEnum.java";

        logicBeans = new ArrayList<>();
        this.initGenConfig();
        this.initEngine();
    }

    /**
     * 初始化模板引擎
     */
    private void initEngine() {
        this.printMessageWithDate("初始化模板引擎 ...");
        this.engine = new Engine();
        this.engine.setToClassPathSourceFactory();
        this.engine.addSharedMethod(new StringKit());
    }

    /**
     * 根據(jù)生成類隊列里面的信息生成代碼
     */
    public void generate() {
        this.printMessageWithDate("開始執(zhí)行生成......");
        this.logicBeans.forEach(this::generateOne);
        this.printMessageWithDate("全部生成 結(jié)束 ...");
        this.printMessageWithDate("==請刷新項目目錄,檢查生成結(jié)果==");
    }

    public void generateOne(LogicBean logicBean) {
        this.printMessageWithDate("正在生成 ===> ["+logicBean.getEntityClass().getName()+"]");
        // 生成枚舉
        this.genTableEnum(logicBean);
        // 生成 Controller 類
        this.genController(logicBean);
        // 生成 Service 類
        this.genService(logicBean);
        // 生成 ServiceImpl 類
        this.genServiceImpl(logicBean);
        // 生成 相應(yīng)的 DTO 類
        this.genReqRespConverter(logicBean);
    }

    public void genTableEnum(LogicBean logicBean) {
        Class<?> entityClass = logicBean.getEntityClass();
        String simpleName = entityClass.getSimpleName();
        this.printMessageWithDate("正在生成 "+simpleName+" 表枚舉類型");
        boolean exist = FileUtil.exist(this.PROJECT_TABLE_ENUM_PATH);
        if (!exist) {
            throw new RuntimeException(this.PROJECT_TABLE_ENUM_PATH + "文件不存在");
        } else {
            List<String> codeLines = FileUtil.readLines(this.PROJECT_TABLE_ENUM_PATH, "UTF-8");
            if (codeLines != null && codeLines.size() != 0) {
                Table annotation = entityClass.getAnnotation(Table.class);
                String reallyName = annotation.name();
                String tableEnumName = StringUtils.upperCase(reallyName);
                String tableEnumDes = logicBean.getTableEnumDes()==null?tableEnumName:logicBean.getTableEnumDes();
                String code = tableEnumName + "(\"" + tableEnumDes + "\"),";
                if (this.checkFileContainCode(codeLines, code)) {
                    this.printMessageWithDate(String.format("TableEnum 文件中已存在 [%s][%s] 的定義,略過",tableEnumName,tableEnumDes));
                } else {
                    int size = codeLines.size();
                    int insertIndex = 0;
                    int startIndex = -1;

                    for(int i = 1; i < size; ++i) {
                        String tmpCode = (String)codeLines.get(i);
                        if (!StringUtils.isBlank(tmpCode)) {
                            if (startIndex == -1 && tmpCode.trim().startsWith("public enum TableEnum")) {
                                startIndex = i + 1;
                            }

                            if (tmpCode.trim().equals(";")) {
                                insertIndex = i;
                            }
                        }
                    }

                    boolean needProcessUpComma = true;
                    if (insertIndex == 0) {
                        insertIndex = startIndex;
                        code = code + ",";
                        needProcessUpComma = false;
                    }

                    if (startIndex > 0) {
                        codeLines.add(insertIndex, "\t" + code);
                        if (needProcessUpComma) {
                            for(int i = insertIndex - 1; i > startIndex; --i) {
                                String tmp = (String)codeLines.get(i);
                                if (!StringUtils.isBlank(tmp)) {
                                    String tmpTrim = tmp.trim();
                                    if (tmpTrim.endsWith(",") || tmpTrim.endsWith(";") || tmpTrim.endsWith("{") || tmpTrim.endsWith("}")) {
                                        break;
                                    }

                                    if (tmpTrim.charAt(tmpTrim.length() - 1) != ',') {
                                        codeLines.set(i, "\t" + tmpTrim + ",");
                                        break;
                                    }
                                }
                            }
                        }

                        this.printMessageWithDate("正在重新生成新的 TableEnum...");
                        FileUtil.writeLines(codeLines, this.PROJECT_TABLE_ENUM_PATH, "UTF-8");
                    }
                }
            } else {
                throw new RuntimeException(this.PROJECT_TABLE_ENUM_PATH + "文件內(nèi)容異常");
            }
        }
    }

    private boolean checkFileContainCode(List<String> codeLines, String code) {
        Iterator<String> var4 = codeLines.iterator();

        String tmpCode;
        String codeLine;
        do {
            if (!var4.hasNext()) {
                return false;
            }

            codeLine = (String)var4.next();
            tmpCode = codeLine.trim();
        } while(StringUtils.isBlank(tmpCode) || tmpCode.startsWith("package ") || tmpCode.startsWith("public class ") || tmpCode.startsWith("return ") || tmpCode.equals("}") || !codeLine.contains(code));

        return true;
    }

    public void genController(LogicBean logicBean) {
        String entityName = logicBean.getEntityClass().getSimpleName();
        String controllerName = entityName + "Controller";
        String controllerFullName = controllerName + ".java";
        String controllerPackagePath = logicBean.getControllerPackagePath();
        this.printMessageWithDate(String.format("正在生成 Controller:%s...", controllerFullName));
        String targetOutputDir = this.getDirFromPackage(controllerPackagePath);
        this.printMessageWithDate(String.format("Controller 生成路徑:%s...", targetOutputDir));
        if (FileUtil.exist(targetOutputDir + File.separator + controllerFullName)) {
            this.printMessageWithDate(String.format("Controller 文件[%s]已存在,忽略生成 ~~ ", controllerFullName));
        } else {
            String serviceName = entityName + "Service";
            Kv<String,Object> data = Kv.by("author", (Object)logicBean.getAuthor())
                    .set("controllerPackagePath", controllerPackagePath)
                    .set("QueryTargetListReq", "Query"+entityName+"ListReq")
                    .set("SaveTargetReq", "Save"+entityName+"Req")
                    .set("ServiceName", serviceName)
                    .set("controllerRemark", logicBean.getTableEnumDes())
                    .set("pathValue", StringUtils.isBlank(logicBean.getPathValue())?toFirstLower(entityName):logicBean.getPathValue())
                    .set("ControllerName", controllerName)
                    .set("serviceName", toFirstLower(serviceName))
                    .set("needPaginate", logicBean.isNeedPaginate())
                    .set("isCRUD",logicBean.isCrudType());
            String content = this.engine.getTemplate(controllerTemplate).renderToString(data);
            this.writeToFile(targetOutputDir, controllerFullName, content);
        }
    }

    private void genService(LogicBean logicBean) {
        Class<?> entityClass = logicBean.getEntityClass();
        String entityName = entityClass.getSimpleName();
        String serviceName = entityName + "Service";
        String serviceFullName = serviceName + ".java";
        String servicePackagePath = logicBean.getServicePackagePath();
        this.printMessageWithDate(String.format("正在生成 Service:%s...", serviceFullName));
        String targetOutputDir = this.getDirFromPackage(servicePackagePath);
        this.printMessageWithDate(String.format("Service 生成路徑:%s...", targetOutputDir));
        if (FileUtil.exist(targetOutputDir + File.separator + serviceFullName)) {
            this.printMessageWithDate(String.format("Service 文件[%s]已存在,忽略生成 ~~ ", serviceFullName));
        } else {
            Kv<String,Object> data = Kv.by("author", (Object)logicBean.getAuthor())
                    .set("servicePackagePath", servicePackagePath)
                    .set("QueryTargetListReq", "Query"+entityName+"ListReq")
                    .set("SaveTargetReq", "Save"+entityName+"Req")
                    .set("EntityName", entityName)
                    .set("QueryTargetListResp", "Query"+entityName+"ListResp")
                    .set("ServiceName", serviceName)
                    .set("needPaginate", logicBean.isNeedPaginate())
                    .set("isCRUD",logicBean.isCrudType());
            String content = this.engine.getTemplate(serviceTemplate).renderToString(data);
            this.writeToFile(targetOutputDir, serviceFullName, content);
        }
    }

    private void genServiceImpl(LogicBean logicBean) {
        Class<?> entityClass = logicBean.getEntityClass();
        String entityName = entityClass.getSimpleName();
        String serviceImplName = entityName + "ServiceImpl";
        String serviceImplFullName = serviceImplName + ".java";
        String serviceImplPackagePath = logicBean.getServiceImplPackagePath();
        this.printMessageWithDate(String.format("正在生成 ServiceImpl:%s...", serviceImplFullName));
        String targetOutputDir = this.getDirFromPackage(serviceImplPackagePath);
        this.printMessageWithDate(String.format("ServiceImpl 生成路徑:%s...", targetOutputDir));
        if (FileUtil.exist(targetOutputDir + File.separator + serviceImplFullName)) {
            this.printMessageWithDate(String.format("ServiceImpl 文件[%s]已存在,忽略生成 ~~ ", serviceImplFullName));
        } else {
            List<Toggle> toggleList = new ArrayList<>();
            Field[] declaredFields = entityClass.getDeclaredFields();
            boolean isSoftDel = false;
            for (Field declaredField : declaredFields) {
                String fieldName = declaredField.getName();
                if ("delFlag".equals(fieldName)) {
                    isSoftDel = true;
                    continue;
                }
                Type genericType = declaredField.getGenericType();
                String[] genericArr = genericType.toString().split("\\.");
                String type = genericArr[genericArr.length - 1];
                if ("Boolean".equals(type)) {
                    toggleList.add(new Toggle(fieldName,"req.get" + toFirstUpper(fieldName) + "()"));
                }
            }

            String keywordSelect = logicBean.getKeywordSelect();
            boolean hasKeyword = false;
            if (StringUtils.isNotBlank(keywordSelect)) {
                // 有關(guān)鍵字,則拼裝查詢條件
                hasKeyword = true;
                String[] keywordArr = keywordSelect.split(",");
                StringBuilder builder = new StringBuilder();
                int i = 0;
                builder.append("criteria.andCondition(\"");
                do {
                    if (i != 0) {
                        builder.append("+ \" or \" + \"");
                    }
                    builder.append(keywordArr[i]).append(" like \" + ").append("\"%\"+").append("req.getKeyword()").append("+\"%\"");
                } while (i++<keywordArr.length-1);
                builder.append(");");
                keywordSelect = builder.toString();
            }

            Kv<String,Object> data = Kv.by("author", (Object)logicBean.getAuthor())
                    .set("serviceImplPackagePath", serviceImplPackagePath)
                    .set("QueryTargetListRespConverter", "Query"+entityName+"ListRespConverter")
                    .set("queryTargetListRespConverter", "query"+entityName+"ListRespConverter")
                    .set("SaveTargetReqConverter", "Save"+entityName+"ReqConverter")
                    .set("saveTargetReqConverter", "save"+entityName+"ReqConverter")
                    .set("QueryTargetListReq", "Query"+entityName+"ListReq")
                    .set("SaveTargetReq", "Save"+entityName+"Req")
                    .set("EntityName", entityName)
                    .set("entityName", toFirstLower(entityName))
                    .set("QueryTargetListResp", "Query"+entityName+"ListResp")
                    .set("ServiceName", entityName + "Service")
                    .set("ServiceImplName", serviceImplName)
                    .set("toggleList", toggleList)
                    .set("hasKeyword", hasKeyword)
                    .set("keywordSelect", keywordSelect)
                    .set("isSoftDel", isSoftDel)
                    .set("needPaginate", logicBean.isNeedPaginate())
                    .set("isCRUD",logicBean.isCrudType());
            String content = this.engine.getTemplate(serviceImplTemplate).renderToString(data);
            this.writeToFile(targetOutputDir, serviceImplFullName, content);
        }
    }

    private void genReqRespConverter(LogicBean logicBean) {
        boolean isCRUD = logicBean.isCrudType();
        if (isCRUD) {
            genRequestBody(logicBean,OptionEnum.SAVE);
            genRequestBody(logicBean,OptionEnum.PAGINATE);
            genResponseBody(logicBean);
        }
    }

    private void genRequestBody(LogicBean logicBean, OptionEnum optionEnum) {
        // 生成請求體
        Class<?> entityClass = logicBean.getEntityClass();
        String entityName = entityClass.getSimpleName();
        String targetReq = null;
        boolean havePageInfo = false;
        switch (optionEnum) {
            case SAVE:
                targetReq = "Save" + entityName + "Req";
                break;
            case PAGINATE:
                targetReq = "Query" + entityName + "ListReq";
                havePageInfo = true;
                break;
            default:
                return;
        }

        String targetReqFullName = targetReq + ".java";
        String requestPackagePath = logicBean.getRequestPackagePath();
        this.printMessageWithDate(String.format("正在生成 RequestBoy:%s...", requestPackagePath));
        String targetOutputDir = this.getDirFromPackage(requestPackagePath);
        this.printMessageWithDate(String.format("RequestBoy 生成路徑:%s...", targetOutputDir));
        if (FileUtil.exist(targetOutputDir + File.separator + targetReqFullName)) {
            this.printMessageWithDate(String.format("RequestBoy 文件[%s]已存在,忽略生成 ~~ ", targetReqFullName));
        } else {
            List<String> inserts = new ArrayList<>();
            // 通過反射獲取目標對象的屬性
            Field[] declaredFields = entityClass.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                String fieldName = declaredField.getName();
                switch (optionEnum) {
                    case SAVE:
                        if ("createTime".equals(fieldName)) continue;
                        if ("updateTime".equals(fieldName)) continue;
                        if ("delFlag".equals(fieldName)) continue;
                        inserts.add("private String " + fieldName + ";");
                        break;
                    case PAGINATE:
                        if ("delFlag".equals(fieldName)) continue;
                        Type genericType = declaredField.getGenericType();
                        String[] genericArr = genericType.toString().split("\\.");
                        String type = genericArr[genericArr.length - 1];
                        if ("Boolean".equals(type)) {
                            inserts.add("private " + type + " " + fieldName + ";");
                        }
                        break;
                }
            }
            if (OptionEnum.PAGINATE == optionEnum) {
                inserts.add("private String keyword;");
            }
            Kv<String,Object> data = Kv.by("author", (Object)logicBean.getAuthor())
                    .set("dtoPackagePath",requestPackagePath)
                    .set("DtoName", targetReq)
                    .set("havePageInfo", havePageInfo)
                    .set("inserts",inserts);
            String content = this.engine.getTemplate(commonDtoTemplate).renderToString(data);
            this.writeToFile(targetOutputDir, targetReqFullName, content);
        }
        if (OptionEnum.SAVE == optionEnum) {
            genConverter(logicBean,targetReq,entityName,requestPackagePath+"."+targetReq);
        }
    }

    private void genResponseBody(LogicBean logicBean) {
        Class<?> entityClass = logicBean.getEntityClass();
        String entityName = entityClass.getSimpleName();
        String targetReq = "Query" + entityName + "ListResp";
        String targetReqFullName = targetReq + ".java";
        String responsePackagePath = logicBean.getResponsePackagePath();
        this.printMessageWithDate(String.format("正在生成 ResponseBoy:%s...", responsePackagePath));
        String targetOutputDir = this.getDirFromPackage(responsePackagePath);
        this.printMessageWithDate(String.format("ResponseBoy 生成路徑:%s...", targetOutputDir));
        if (FileUtil.exist(targetOutputDir + File.separator + targetReqFullName)) {
            this.printMessageWithDate(String.format("ResponseBoy 文件[%s]已存在,忽略生成 ~~ ", targetReqFullName));
        } else {
            List<String> inserts = new ArrayList<>();
            Field[] declaredFields = entityClass.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                String fieldName = declaredField.getName();
                if ("createTime".equals(fieldName)) continue;
                if ("updateTime".equals(fieldName)) continue;
                if ("delFlag".equals(fieldName)) continue;
                Type genericType = declaredField.getGenericType();
                String[] genericArr = genericType.toString().split("\\.");
                String type = genericArr[genericArr.length - 1];
                inserts.add("private " + type + " " + fieldName + ";");
            }
            Kv<String,Object> data = Kv.by("author", (Object)logicBean.getAuthor())
                    .set("dtoPackagePath",responsePackagePath)
                    .set("DtoName", targetReq)
                    .set("inserts",inserts);
            String content = this.engine.getTemplate(queryDtoTemplate).renderToString(data);
            this.writeToFile(targetOutputDir, targetReqFullName, content);
        }
        genConverter(logicBean,targetReq,entityName,responsePackagePath+"."+targetReq);
    }

    private void genConverter(LogicBean logicBean,String A,String B,String AAllPath) {
        String converterName = A + "Converter";
        String converterFullName = converterName + ".java";
        String converterPackagePath = logicBean.getConverterPackagePath();
        this.printMessageWithDate(String.format("正在生成 Converter:%s...", converterPackagePath));
        String targetOutputDir = this.getDirFromPackage(converterPackagePath);
        this.printMessageWithDate(String.format("Converter 生成路徑:%s...", targetOutputDir));
        if (FileUtil.exist(targetOutputDir + File.separator + converterFullName)) {
            this.printMessageWithDate(String.format("Converter 文件[%s]已存在,忽略生成 ~~ ", converterFullName));
        } else {
            Kv<String,Object> data = Kv.by("author", (Object)logicBean.getAuthor())
                    .set("converterPackagePath", converterPackagePath)
                    .set("Target", A)
                    .set("EntityName", B)
                    .set("TargetAllPath",AAllPath);
            String content = this.engine.getTemplate(converterTemplate).renderToString(data);
            this.writeToFile(targetOutputDir, converterFullName, content);
        }
    }

    private void writeToFile(String targetOutputDir, String fileName, String content) {
        if (!FileUtil.exist(targetOutputDir)) {
            FileUtil.mkdir(targetOutputDir);
        }

        String target = targetOutputDir + File.separator + fileName;
        if (!FileUtil.exist(target)) {
            FileUtil.writeUtf8String(content, target);
        }
    }

    /**
     * 初始化配置,供子類實現(xiàn)
     */
    public void initGenConfig() {
    }

    private String getDirFromPackage(String targetPackageName) {
        return PROJECT_PATH + "/src/main/java/" + targetPackageName.replace(".", "/");
    }

    public void printMessageWithDate(String message) {
        System.out.println("[Generate Log]:[" + DateUtil.format(new Date(), DateUtil.YMDHMS) + "]" + message);
    }

    public String toFirstLower(String var) {
        return var.substring(0, 1).toLowerCase() + var.substring(1);
    }

    public String toFirstUpper(String var) {
        return var.substring(0, 1).toUpperCase() + var.substring(1);
    }

    public void addGenBean(LogicBean logicBean) {
        this.logicBeans.add(logicBean);
    }

    private enum OptionEnum {
        SAVE,
        PAGINATE;
    }

    public static class Toggle {
        private String key;
        private String value;

        public Toggle() {
        }

        public Toggle(String key, String value) {
            this.key = key;
            this.value = value;
        }

        public String getKey() {
            return key;
        }

        public void setKey(String key) {
            this.key = key;
        }

        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }
    }

    public static class StringKit extends StrKit {
        private static final long serialVersionUID = -808251639784763326L;
    }

    /**
     * 生成對象類
     */
    public static class LogicBean {
        private Class<?> entityClass;
        private String author;
        private String projectPath;
        private String controllerPackagePath;
        private String servicePackagePath;
        private String serviceImplPackagePath;
        private String converterPackagePath;
        private String requestPackagePath;
        private String responsePackagePath;
        private String pathValue;
        private String keywordSelect;
        private boolean isCrudType;
        private boolean needPaginate;
        private boolean needSort;
        private String tableEnumDes;

        public LogicBean() {
        }

        public LogicBean(Class<?> entityClass,
                         String tableDes,
                         String author,
                         String pathValue,
                         String controllerPackagePath,
                         String servicePackagePath,
                         String serviceImplPackagePath,
                         String converterPackagePath,
                         String requestPackagePath,
                         String responsePackagePath,
                         String keywordSelect,
                         boolean needPaginate,
                         boolean isCrudType) {
            if (!entityClass.isAnnotationPresent(Table.class)) {
                throw new RuntimeException("Entity:" + entityClass.getName() + "必須有 @Table 注解");
            }
            this.entityClass = entityClass;
            this.tableEnumDes = tableDes;
            this.author = author;
            this.pathValue = pathValue;
            this.controllerPackagePath = controllerPackagePath;
            this.servicePackagePath = servicePackagePath;
            this.serviceImplPackagePath = serviceImplPackagePath;
            this.converterPackagePath = converterPackagePath;
            this.requestPackagePath = requestPackagePath;
            this.responsePackagePath = responsePackagePath;
            this.keywordSelect = keywordSelect;
            this.needPaginate = needPaginate;
            this.isCrudType = isCrudType;

        }

        public Class<?> getEntityClass() {
            return entityClass;
        }

        public void setEntityClass(Class<?> entityClass) {
            if (!entityClass.isAnnotationPresent(Table.class)) {
                throw new RuntimeException("Entity:" + entityClass.getName() + "必須有 @Table 注解");
            }
            this.entityClass = entityClass;
        }

        public String getAuthor() {
            return author;
        }

        public void setAuthor(String author) {
            this.author = author;
        }

        public String getProjectPath() {
            return projectPath;
        }

        public void setProjectPath(String projectPath) {
            this.projectPath = projectPath;
        }

        public String getControllerPackagePath() {
            return controllerPackagePath;
        }

        public void setControllerPackagePath(String controllerPackagePath) {
            this.controllerPackagePath = controllerPackagePath;
        }

        public String getServicePackagePath() {
            return servicePackagePath;
        }

        public void setServicePackagePath(String servicePackagePath) {
            this.servicePackagePath = servicePackagePath;
        }

        public String getServiceImplPackagePath() {
            return serviceImplPackagePath;
        }

        public void setServiceImplPackagePath(String serviceImplPackagePath) {
            this.serviceImplPackagePath = serviceImplPackagePath;
        }

        public String getTableEnumDes() {
            return tableEnumDes;
        }

        public void setTableEnumDes(String tableEnumDes) {
            this.tableEnumDes = tableEnumDes;
        }

        public boolean isCrudType() {
            return isCrudType;
        }

        public void setCrudType(boolean crudType) {
            isCrudType = crudType;
        }

        public boolean isNeedPaginate() {
            return needPaginate;
        }

        public void setNeedPaginate(boolean needPaginate) {
            this.needPaginate = needPaginate;
        }

        public String getPathValue() {
            return pathValue;
        }

        public void setPathValue(String pathValue) {
            this.pathValue = pathValue;
        }

        public boolean isNeedSort() {
            return needSort;
        }

        public void setNeedSort(boolean needSort) {
            this.needSort = needSort;
        }

        public String getConverterPackagePath() {
            return converterPackagePath;
        }

        public void setConverterPackagePath(String converterPackagePath) {
            this.converterPackagePath = converterPackagePath;
        }

        public String getRequestPackagePath() {
            return requestPackagePath;
        }

        public void setRequestPackagePath(String requestPackagePath) {
            this.requestPackagePath = requestPackagePath;
        }

        public String getResponsePackagePath() {
            return responsePackagePath;
        }

        public void setResponsePackagePath(String responsePackagePath) {
            this.responsePackagePath = responsePackagePath;
        }

        public String getKeywordSelect() {
            return keywordSelect;
        }

        public void setKeywordSelect(String keywordSelect) {
            this.keywordSelect = keywordSelect;
        }
    }

    public static class Kv<K,V> extends HashMap<K,V> {

        private static final long serialVersionUID = -808251639784763326L;

        public Kv() {
        }

        public Kv<K,V> set(K key, V value) {
            super.put(key, value);
            return this;
        }

        public Kv<K,V> set(Map<K,V> map) {
            super.putAll(map);
            return this;
        }

        public Kv<K,V> delete(K key) {
            super.remove(key);
            return this;
        }

        public static <K,V> Kv<K,V> by(K key, V value) {
            return (new Kv<K,V>()).set(key, value);
        }

        public static <K,V> Kv<K,V> create() {
            return new Kv<K,V>();
        }

        public Kv<K,V> setIfNotNull(K key, V value) {
            if (value != null) {
                this.set(key, value);
            }
            return this;
        }

    }
}

MainLogicGenerator.java 代碼如下:

package com.duojiala.mikeboot.extend.gen;

import com.duojiala.mikeboot.domain.entity.Post;

/**
 * Controller Service 代碼生成器
 */
public class MainLogicGenerator extends LogicGenerator {
    public static void main(String[] args) {
        new MainLogicGenerator().generate();
    }

    @Override
    public void initGenConfig() {
        // 以哪個實體類生成代碼
        Class<?> clazz = Post.class;
        // 表描述
        String tableDes = "職位";  // TableEnum.java 類中的枚舉,tableDes 為表的描述信息,如果不填默認為類名的全大寫
        // 生成 java 代碼的默認作者
        String author = "mike";
        // 映射路徑
        String pathValue = ""; // 如果不填默認為類的全小寫
        // Controller 類生成路徑
        String controllerPackagePath = "com.duojiala.mikeboot.controller";
        // Service 類生成路徑
        String servicePackagePath = "com.duojiala.mikeboot.service";
        // ServiceImpl 類生成路徑
        String serviceImplPackagePath = "com.duojiala.mikeboot.service.imp";
        // Converter 轉(zhuǎn)換器類生成路徑
        String converterPackagePath = "com.duojiala.mikeboot.domain.converter.subclass";
        // Request 請求體類生成路徑
        String requestPackagePath = "com.duojiala.mikeboot.domain.req";
        // Response 響應(yīng)體類生成路徑
        String responsePackagePath = "com.duojiala.mikeboot.domain.resp";
        // 是否需要分頁查詢
        boolean needPaginate = true;
        // 是否為普通增刪操作
        boolean isCrudType = true;
        // 以那些字段進行模糊查詢,多個用 "," 拼接,不設(shè)置默認為 name
        String keywordSelect = "";

        //創(chuàng)建主邏輯生成配置Bean
        LogicBean logicBean = new LogicBean(clazz, tableDes, author, pathValue,
                controllerPackagePath, servicePackagePath, serviceImplPackagePath,converterPackagePath,requestPackagePath,responsePackagePath,keywordSelect, needPaginate, isCrudType);

        //加入到生成隊列中
        addGenBean(logicBean);
    }
}


4. 測試效果

我只需要在 MainLogicGenerator 啟動類中配置相應(yīng)的信息,比如要生成哪張表的增刪改查,就配置對應(yīng)的 .class,設(shè)置一些作者、路徑、是否生成分頁的方法等等條件,運行 mian 方法就行了。

Java-代碼生成器的實現(xiàn)
例如上面所示,我要生成 post 表相關(guān)的一些基本操作,而且可以看到我這里是沒有 Controller、Service、ServiceImpl 等文件的。

Java-代碼生成器的實現(xiàn)

可以看到運行之后這些文件也都生成了。


三、項目源碼

這就是我寫的代碼生成器,以下我也提供了項目的下載鏈接便于大家去玩:

鏈接:百度網(wǎng)盤
提取碼:xb1k

其次我還要說明一點,如果你想要連那些實體類的文件和前端也全部生成,建議你還是看看 jfinal 是怎么實現(xiàn)的。文章來源地址http://www.zghlxwxcb.cn/news/detail-431697.html

到了這里,關(guān)于Java-代碼生成器的實現(xiàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔相關(guān)法律責任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • MyBatisPlus代碼生成器使用

    MyBatisPlus代碼生成器使用

    無侵入 :只做增強不做改變,引入它不會對現(xiàn)有工程產(chǎn)生影響,如絲般順滑 損耗小 :啟動即會自動注入基本 CURD,性能基本無損耗,直接面向?qū)ο蟛僮?強大的 CRUD 操作 :內(nèi)置通用 Mapper、通用 Service,僅僅通過少量配置即可實現(xiàn)單表大部分 CRUD 操作,更有強大的條件構(gòu)造器,

    2024年02月03日
    瀏覽(96)
  • css在線代碼生成器

    css在線代碼生成器

    這里收集了許多有意思的css效果在線代碼生成器適合每一位前端開發(fā)者 網(wǎng)格生成器https://cssgrid-generator.netlify.app/ CSS Grid Generator可幫助開發(fā)人員使用CSS Grid創(chuàng)建復(fù)雜的網(wǎng)格布局。網(wǎng)格布局是創(chuàng)建Web頁面的靈活和響應(yīng)式設(shè)計的強大方式。 布局生成器https://layout.bradwoods.io/ CSS布局生

    2024年02月14日
    瀏覽(100)
  • 源生成器:根據(jù)需要自動生成機械重復(fù)代碼

    本文概述了利用.NET Compiler Platform(“Roslyn”)SDK 附帶的 源生成器 (Source Generator)自動生成機械重復(fù)的代碼。關(guān)于這部分的基礎(chǔ)入門知識可以在MSDN [1] 學(xué)到。 本文 默認 已經(jīng)有一個解決方案,包含兩個項目。一個是普通C#項目,依賴于另一個源生成器項目。 此處以 Dependenc

    2024年02月01日
    瀏覽(94)
  • 【安全密鑰】對基爾霍夫-洛-約翰遜噪聲(KLJN)安全密鑰交換協(xié)議的統(tǒng)計隨機數(shù)生成器攻擊(Matlab代碼實現(xiàn))

    【安全密鑰】對基爾霍夫-洛-約翰遜噪聲(KLJN)安全密鑰交換協(xié)議的統(tǒng)計隨機數(shù)生成器攻擊(Matlab代碼實現(xiàn))

    ???????? 歡迎來到本博客 ???????? ??博主優(yōu)勢: ?????? 博客內(nèi)容盡量做到思維縝密,邏輯清晰,為了方便讀者。 ?? 座右銘: 行百里者,半于九十。 ?????? 本文目錄如下: ?????? 目錄 ??1 概述 ??2 運行結(jié)果 ??3?參考文獻 ??4 Matlab代碼、數(shù)據(jù)、文章

    2024年04月12日
    瀏覽(28)
  • 【若依】-代碼生成器-微服務(wù)版本

    【若依】-代碼生成器-微服務(wù)版本

    代碼生成器可以快速的構(gòu)建單表的CRUD操作,甚至提供了前端,可謂良心 說明:我默認你已經(jīng)部署了微服務(wù)版本的若依項目,并將前后端已經(jīng)運行 下面的內(nèi)容是關(guān)于-單表CRUD-的代碼生成器使用步驟 這里的表可以直接從若依的官網(wǎng)拿,改表名就可以,改一下表名,導(dǎo)入數(shù)據(jù)庫

    2024年02月04日
    瀏覽(94)
  • 若依筆記(四):代碼生成器

    若依筆記(四):代碼生成器

    已知使用MyBatisPlus代碼生成器可以自動生成Entity、Mapper、Service、Controller代碼,前提是數(shù)據(jù)庫中有數(shù)據(jù)表,生成pojo類以及對于該數(shù)據(jù)表的增刪改查命令的代碼,若依更進一步能選擇表后生成代碼、預(yù)覽、下載,同時可以生產(chǎn)前端代碼,與后端一起還能實現(xiàn)權(quán)限管理和數(shù)據(jù)隔離

    2024年02月06日
    瀏覽(89)
  • 基于Velocity開發(fā)代碼生成器

    基于Velocity開發(fā)代碼生成器

    一、引言 在項目開發(fā)中,我們有碰到大量的簡單、重復(fù)的增刪改查需求,通過閱讀 若依 框架https://github.com/yangzongzhuan/RuoYi?的代碼生成器實現(xiàn),結(jié)合我項目所用的技術(shù)棧,開發(fā)出本項目的代碼生成器。 二、Velocity?簡單介紹 1、Velocity是一個基于Java的模板引擎,我們可以往C

    2024年02月15日
    瀏覽(89)
  • 又一款低代碼生成器

    大家好,我是 Java陳序員 。 最近在瀏覽 Github 的時候,發(fā)現(xiàn)了一款簡單好用的低代碼生成器 maku-generator 。 無論是工作,還是接私活,這款低代碼都能快速迭代出一個項目! 今天,分享給大家,強烈建議私有化部署! 關(guān)注微信公眾號:【Java陳序員】,獲取 開源項目分享、

    2024年04月10日
    瀏覽(91)
  • C# 源代碼生成器

    C# 源代碼生成器

    源生成器 是由 .NET Compiler Platform(“Roslyn”) SDK 附帶。 通過源生成器,C# 開發(fā)人員可以在編譯用戶代碼時檢查用戶代碼。 生成器可以動態(tài)創(chuàng)建新的 C# 源文件,這些文件將添加到用戶的編譯中。 這樣,代碼可以在編譯期間運行。 它會檢查你的程序以生成與其余代碼一起編譯

    2024年02月04日
    瀏覽(88)
  • Vform低代碼表單、表單生成器

    Vform - 讓表單開發(fā)變得簡單Vform 是一個基于 Vue.js 的前端表單生成器,使用它可以通過可視化拖拽的方式快速構(gòu)建出功能完備的表單。相比手寫模板代碼,它可以大大提高表單開發(fā)的效率和體驗。 Vform 的主要特性如下: 拖拽布局:通過鼠標拖拽可以隨意布置各種表單控件,無需編寫

    2024年02月16日
    瀏覽(85)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包