BeetlSql&&ProtoBuffer实践Demo

前言

由于开发架构问题,公司使用的基于SpringBoot+BeetlSql+ProtoBuffer的架子来处理业务,由于BeetlSQL和Protobuffer有些小众,特记录一下。

项目架构介绍

  • 本文采用SpringBoot+BeetlSQL+ProtoBuffer进行快速搭建开发
  • PDMAN表快速设计与维护

项目准备工作

SQL语句

员工信息表

CREATE TABLE `inter_employee` (
  `id` varchar(32) NOT NULL COMMENT 'id',
  `name` varchar(32) NOT NULL COMMENT '姓名',
  `email` varchar(128) NOT NULL COMMENT '邮箱',
  `sex` char(1) NOT NULL COMMENT '性别 0-男;1-女;2-未知',
  `dept_id` bigint NOT NULL COMMENT '所属部门id',
  `org_id` varchar(32) NOT NULL COMMENT '所属机构id',
  `status` char(1) NOT NULL COMMENT '状态 0-无效;1-有效',
  `created_by` varchar(32) DEFAULT NULL COMMENT '创建人',
  `created_time` datetime DEFAULT NULL COMMENT '创建时间',
  `updated_by` varchar(32) DEFAULT NULL COMMENT '更新人',
  `updated_time` datetime DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='机构员工信息';

员工岗位表

CREATE TABLE `inter_employee_job_duty` (
  `id` varchar(32) NOT NULL COMMENT 'id',
  `emp_id` varchar(32) NOT NULL COMMENT '员工ID',
  `emp_job` varchar(32) NOT NULL COMMENT '员工岗位 CODE-开发;HR-人事经理',
  `job_name` varchar(32) NOT NULL COMMENT '岗位名称 CODE-开发;HR-人事经理',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='机构员工岗位表 员工可能会存在多个岗位,权限以角色为主,数据权限以岗位为主,员工-岗位:one2many关系';

相关环境

  • jdk 1.8+
  • mysql 5.7
  • Lombok

项目案例

项目层级

├── pom.xml
├── src
│   ├── main
│   │   ├── Doc
│   │   ├── java
│   │   ├── protobuf
│   │   └── resources
│   └── test
│       └── java

相关依赖

引入springboot父项目依赖

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
    </parent>

设置相关版本

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <log4jdbc.version>1.16</log4jdbc.version>
        <fastjson.version>1.2.54</fastjson.version>
        <druid.version>1.1.14</druid.version>
    </properties>

引入相关依赖

    <dependencies>
        <!--Spring boot start -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--Spring boot end -->
        <!-- beetlSql -->
        <dependency>
            <groupId>com.ibeetl</groupId>
            <artifactId>beetl-framework-starter</artifactId>
            <version>1.1.81.RELEASE</version>
        </dependency>
        <!--监控sql日志 -->
        <dependency>
            <groupId>org.bgee.log4jdbc-log4j2</groupId>
            <artifactId>log4jdbc-log4j2-jdbc4.1</artifactId>
            <version>${log4jdbc.version}</version>
        </dependency>
        <!--Mysql依赖包 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!-- druid数据源驱动 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid.version}</version>
        </dependency>
        <!--lombok插件 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>
        <!--google protobuf 引用 -->
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>3.6.1</version>
        </dependency>
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java-util</artifactId>
            <version>3.6.1</version>
        </dependency>
    </dependencies>

引入ProtoBuffer和Maven编译工具

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <!-- 跳过单元测试 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <skipTests>true</skipTests>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <version>3.0.2</version>
                <!-- set encoding to something not platform dependent -->
                <configuration>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>com.github.os72</groupId>
                <artifactId>protoc-jar-maven-plugin</artifactId>
                <version>3.7.0.2</version>
                <executions>
                    <execution>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>run</goal>
                        </goals>
                        <configuration>
                            <addProtoSources>all</addProtoSources>
                            <includeStdTypes>true</includeStdTypes>
                            <includeMavenTypes>direct</includeMavenTypes>
                            <!-- <type>java-shaded</type> <addSources>none</addSources> -->
                            <outputDirectory>src/main/java</outputDirectory>
                            <includeDirectories>
                                <!-- <include>com/idasound/protobuf</include> -->
                            </includeDirectories>
                            <inputDirectories>
                                <include>src/main/protobuf</include>
                                <!-- <include>${project.basedir}</include> -->
                            </inputDirectories>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
        <pluginManagement>
            <plugins>
                <!--This plugin's configuration is used to store Eclipse m2e settings
                    only. It has no influence on the Maven build itself. -->
                <plugin>
                    <groupId>org.eclipse.m2e</groupId>
                    <artifactId>lifecycle-mapping</artifactId>
                    <version>1.0.0</version>
                    <configuration>
                        <lifecycleMappingMetadata>
                            <pluginExecutions>
                                <pluginExecution>
                                    <pluginExecutionFilter>
                                        <groupId>
                                            com.github.os72
                                        </groupId>
                                        <artifactId>
                                            protoc-jar-maven-plugin
                                        </artifactId>
                                        <versionRange>
                                            [3.6.0.1,)
                                        </versionRange>
                                        <goals>
                                            <goal>run</goal>
                                        </goals>
                                    </pluginExecutionFilter>
                                    <action>
                                        <ignore></ignore>
                                    </action>
                                </pluginExecution>
                            </pluginExecutions>
                        </lifecycleMappingMetadata>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>

相关配置

配置application.yml文件
spring:
  profiles:
    active: dev
  application:
    name: beetlSqlDemo-api

server:
  servlet:
    context-path: /beetlSqlDemo
配置dev【application-dev.yml】环境
server:
  port: 8080
  tomcat:
    max-swallow-size: 100MB

#配置数据源
spring:
  datasource:
    druid:
      type: com.alibaba.druid.pool.DruidDataSource
      driverClassName: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
      url: jdbc:log4jdbc:mysql://localhost:3306/beetsql_Demo?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false&&allowPublicKeyRetrieval=true
      username: root
      password: 12345678

      # 初始化连接大小
      initial-size: 5
      # 最小空闲连接数
      min-idle: 5
      max-active: 30
      max-wait: 60000
      # 可关闭的空闲连接间隔时间
      time-between-eviction-runs-millis: 60000
      # 配置连接在池中的最小生存时间
      min-evictable-idle-time-millis: 300000
      validation-query: select '1' from dual
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      # 打开PSCache,并且指定每个连接上PSCache的大小
      pool-prepared-statements: true
      max-open-prepared-statements: 50
      max-pool-prepared-statement-per-connection-size: 20
      # 配置监控统计拦截的filters
      filters: stat

      stat-view-servlet:
        url-pattern: /druid/*
        reset-enable: false
        login-username: admin
        login-password: 123456

      web-stat-filter:
        url-pattern: /*
        exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"

  servlet:
    multipart:
      max-file-size: 50MB
      max-request-size: 50MB

sys:
  log:
    isSaveLog: true

logging:
  file: D:/logs/beetsqlDemo/dev/beetsqlDemo.log
  config: classpath:logback-dev.xml
配置日志
  • 配置logback文件

    • 创建log4jdbc.log4j2.properties文件

    • 配置如下属性:

      # If you use SLF4J. First, you need to tell log4jdbc-log4j2 that you want to use the SLF4J logger
      log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator
      
  • 配置logback

    • 创建logback-dev.xml

    • 配置如下属性:

      <?xml version="1.0" encoding="UTF-8"?>
      <configuration scan="true" scanPeriod="60 seconds" debug="false">
          <contextName>beetlSqlDemo</contextName>
          <property name="LOG_HOME" value="D:/logs/beetSqlDemo/dev"/>
          <property name="LOG_NAME" value="beetlSqlDemo"/>
          <!--<springProperty scope="context" name="LOG_HOME" source="sys.log.output.path"/>-->
      
          <!--输出到控制台-->
          <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
              <encoder>
                  <pattern>%black(%contextName-) %red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}) - %gray(%msg%n)</pattern>
                  <charset>utf-8</charset>
              </encoder>
          </appender>
      
          <appender name="FILE_LOG_OUT" class="ch.qos.logback.core.rolling.RollingFileAppender">
              <file>${LOG_HOME}/${LOG_NAME}.log</file>
              <append>true</append>
              <encoder>
                  <pattern>%contextName- %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %class{36} %L %M - %msg%xEx%n</pattern>
              </encoder>
              <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
                  <fileNamePattern>${LOG_HOME}${file.separator}${LOG_NAME}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
                  <maxHistory>180</maxHistory>
                  <maxFileSize>100MB</maxFileSize>
              </rollingPolicy>
          </appender>
      
          <!--普通日志输出到控制台-->
          <root level="info">
              <appender-ref ref="console" />
          </root>
      
          <logger name="com.wabestway" level="INFO">
              <appender-ref ref="FILE_LOG_OUT"/>
          </logger>
          <logger name="com.idasound.isb" level="INFO">
              <appender-ref ref="FILE_LOG_OUT"/>
          </logger>
      
          <!--监控sql日志输出 -->
          <logger name="jdbc.sqlonly" level="INFO" additivity="false">
              <appender-ref ref="console" />
              <appender-ref ref="FILE_LOG_OUT"/>
          </logger>
      
          <logger name="jdbc.resultset" level="ERROR" additivity="false">
              <appender-ref ref="console" />
              <appender-ref ref="FILE_LOG_OUT"/>
          </logger>
      
          <logger name="jdbc.resultsettable" level="OFF" additivity="false">
              <appender-ref ref="console" />
          </logger>
      
          <logger name="jdbc.connection" level="OFF" additivity="false">
              <appender-ref ref="console" />
          </logger>
      
          <logger name="jdbc.sqltiming" level="OFF" additivity="false">
              <appender-ref ref="console" />
          </logger>
      
          <logger name="jdbc.audit" level="OFF" additivity="false">
              <appender-ref ref="console" />
          </logger>
      </configuration>
      
      

Demo实现

Model

创建员工信息

package com.bossyang.beetsqlDemo.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.beetl.sql.core.annotatoin.AssignID;
import org.beetl.sql.core.annotatoin.Table;

import java.util.Date;
import java.util.List;

/**
 * 机构员工信息
 * @Title: InterEmployee.java
 * @author: 
 * @date: 2020年05月31日 13:43:25
 * @version V1.0
 */
@Data
@Table(name="inter_employee")
public class InterEmployee {
    @AssignID
    private String id; //id
    private String name; //姓名
    private String email; //邮箱
    private String sex; //性别 0-男;1-女;2-未知
    private Long deptId; //所属部门id
    private String orgId; //所属机构id
    private String status; //状态 0-无效;1-有效
    private String createdBy; //创建人
    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
    private Date createdTime; //创建时间
    private String updatedBy; //更新人
    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
    private Date updatedTime; //更新时间
    private List<String> jobDuties;

}

创建岗位表

package com.bossyang.beetsqlDemo.model;

import lombok.Data;
import org.beetl.sql.core.annotatoin.AssignID;
import org.beetl.sql.core.annotatoin.Table;
/**
 * 机构员工岗位表 员工可能会存在多个岗位,权限以角色为主,数据权限以岗位为主,员工-岗位:one2many关系
 * @Title: InterEmployeeJobDuty.java
 * @author: 
 * @date: 2020年05月31日 16:24:13
 * @version V1.0
 */
@Data
@Table(name="inter_employee_job_duty")
public class InterEmployeeJobDuty {
    @AssignID
    private String id; //id
    private String empId; //员工ID
    private String empJob; //员工岗位 CODE-开发;HR-人事经理
    private String jobName; //岗位名称 CODE-开发;HR-人事经理
}

SQL文件

**BeetlSql的SQL文件扫描默认路径为resources/sql,所以我们为了简便开发使用Beetlsql的默认配置。

  • 在resources下创建一个名字为sql文件夹

  • 增加文件,实现相关业务

    • interEmployeeExtra.md

      listCols
      ===
          t.id id, t.name name, t.email email, t.sex sex, t.dept_id deptId, t.org_id orgId, t.status status
          , t.created_by createdBy, t.created_time createdTime, t.updated_by updatedBy, t.updated_time updatedTime
          
      queryByCondition
      ===
          select #use("listCols")# 
          from inter_employee t
          where #use("condition")# 
          
      queryByCondition$count
      ===
          select count(1) total,count(t.id) empNums
          from inter_employee t
          where #use("condition")# 
          
            
      condition
      ===
          1 = 1  
          @if(!isEmpty(id)){
              and t.id = #id#
          @}
          @if(!isEmpty(name)){
              and t.name = #name#
          @}
          @if(!isEmpty(email)){
              and t.email = #email#
          @}
          @if(!isEmpty(sex)){
              and t.sex = #sex#
          @}
          @if(!isEmpty(deptId)){
              and t.dept_id = #deptId#
          @}
          @if(!isEmpty(orgId)){
              and t.org_id = #orgId#
          @}
          @if(!isEmpty(status)){
              and t.status = #status#
          @}
          @if(!isEmpty(createdBy)){
              and t.created_by = #createdBy#
          @}
          @if(!isEmpty(createdTime)){
              and t.created_time = #createdTime#
          @}
          @if(!isEmpty(updatedBy)){
              and t.updated_by = #updatedBy#
          @}
          @if(!isEmpty(updatedTime)){
              and t.updated_time = #updatedTime#
          @}
          @if(!isEmpty(jobDuties)){
              and EXISTS (SELECT 1 FROM inter_employee_job_duty jd WHERE jd.emp_id = t.id AND jd.emp_job IN (#join(jobDuties)#))
          @}
          
      delInterEmployeeById
      ===
      * 逻辑删除
       
          update inter_employee t set t.status = '0', t.updated_by = #updatedBy#, t.updated_time = now() where t.id = #id#
          
      batchDelInterEmployeeByIds
      ===
      * 批量逻辑删除
       
          update inter_employee t set t.status = '0', t.updated_by = #updatedBy#, t.updated_time = now() where t.id  in( #join(ids)#)
      
    • interEmployeeJobDutyExtra.md

      listCols
      ===
          t.id id, t.emp_id empId, t.emp_job empJob, t.job_name jobName
          
      queryByCondition
      ===
          select #use("listCols")# 
          from inter_employee_job_duty t
          where #use("condition")# 
          
      queryByCondition$count
      ===
          select count(1)
          from inter_employee_job_duty t
          where #use("condition")# 
          
            
      condition
      ===
          1 = 1  
          @if(!isEmpty(id)){
              and t.id = #id#
          @}
          @if(!isEmpty(empId)){
              and t.emp_id = #empId#
          @}
          @if(!isEmpty(empJob)){
              and t.emp_job = #empJob#
          @}
          @if(!isEmpty(jobName)){
              and t.job_name = #jobName#
          @}
          
      delInterEmployeeJobDutyById
      ===
      * 逻辑删除
       
          update inter_employee_job_duty t set t.status = '0', t.updated_by = #updatedBy#, t.updated_time = now() where t.id = #id#
          
      batchDelInterEmployeeJobDutyByIds
      ===
      * 批量逻辑删除
       
          update inter_employee_job_duty t set t.status = '0', t.updated_by = #updatedBy#, t.updated_time = now() where t.id  in( #join(ids)#)
      

DAO

创建repository文件夹

  • InterEmployeeDao
package com.bossyang.beetsqlDemo.repository;

import com.bossyang.beetsqlDemo.ext.MyBaseMapper;
import com.bossyang.beetsqlDemo.model.InterEmployee;
import org.beetl.sql.core.annotatoin.SqlResource;
import org.beetl.sql.core.annotatoin.SqlStatement;
import org.beetl.sql.core.engine.PageQuery;

import java.util.List;

/**
 * InterEmployee Dao
 * @author: 
 * @date: 2020年05月31日 13:43:25
 * @version V1.0
 */
@SqlResource("interEmployeeExtra")
public interface InterEmployeeDao extends MyBaseMapper<InterEmployee> {
    /**
     * 分页查询
     * @Title: queryByCondition
     * @param: query      
     * @return: PageQuery 
     * @throws
     */
    public PageQuery<InterEmployee> queryByCondition(PageQuery query);
    
    /**
     * 不分页查询
     * @Title: queryByCondition
     * @param: interEmployee      
     * @return: List 
     * @throws
     */
    public List<InterEmployee> queryByCondition(InterEmployee interEmployee);
    
    /**
     * 根据id逻辑删除
     * @Title: queryByCondition
     * @param: interEmployee      
     * @return: int 
     * @throws
     */
    @SqlStatement(params = "id,updatedBy")
    public int delInterEmployeeById(String id, String updatedBy);
    
    /**
     * 批量逻辑删除
     * @Title: batchDelAppUserByIds
     * @param: ids
     * @param: updatedBy
     * @return: int 
     * @throws
     */
    @SqlStatement(params = "ids,updatedBy")
    public int batchDelInterEmployeeByIds(List<String> ids, String updatedBy);
}
  • InterEmployeeJobDutyDao
package com.bossyang.beetsqlDemo.repository;

import com.bossyang.beetsqlDemo.model.InterEmployeeJobDuty;
import org.beetl.sql.core.annotatoin.SqlResource;
import org.beetl.sql.core.annotatoin.SqlStatement;
import org.beetl.sql.core.engine.PageQuery;
import org.beetl.sql.core.mapper.BaseMapper;

import java.util.List;

/**
 * InterEmployeeJobDuty Dao
 * @author: 
 * @date: 2020年05月31日 16:24:13
 * @version V1.0
 */
@SqlResource("interEmployeeJobDutyExtra")
public interface InterEmployeeJobDutyDao extends BaseMapper<InterEmployeeJobDuty> {
    /**
     * 分页查询
     * @Title: queryByCondition
     * @param: query      
     * @return: PageQuery 
     * @throws
     */
    public PageQuery<InterEmployeeJobDuty> queryByCondition(PageQuery query);
    
    /**
     * 不分页查询
     * @Title: queryByCondition
     * @param: interEmployeeJobDuty      
     * @return: List 
     * @throws
     */
    public List<InterEmployeeJobDuty> queryByCondition(InterEmployeeJobDuty interEmployeeJobDuty);
    
    /**
     * 根据id逻辑删除
     * @Title: queryByCondition
     * @param: interEmployeeJobDuty      
     * @return: int 
     * @throws
     */
    @SqlStatement(params = "id,updatedBy")
    public int delInterEmployeeJobDutyById(String id, String updatedBy);
    
    /**
     * 批量逻辑删除
     * @Title: batchDelAppUserByIds
     * @param: ids
     * @param: updatedBy
     * @return: int 
     * @throws
     */
    @SqlStatement(params = "ids,updatedBy")
    public int batchDelInterEmployeeJobDutyByIds(List<String> ids, String updatedBy);
}

Service

接口定义
  • InterEmployeeService
package com.bossyang.beetsqlDemo.repository;

import com.bossyang.beetsqlDemo.model.InterEmployeeJobDuty;
import org.beetl.sql.core.annotatoin.SqlResource;
import org.beetl.sql.core.annotatoin.SqlStatement;
import org.beetl.sql.core.engine.PageQuery;
import org.beetl.sql.core.mapper.BaseMapper;

import java.util.List;

/**
 * InterEmployeeJobDuty Dao
 * @author: 
 * @date: 2020年05月31日 16:24:13
 * @version V1.0
 */
@SqlResource("interEmployeeJobDutyExtra")
public interface InterEmployeeJobDutyDao extends BaseMapper<InterEmployeeJobDuty> {
    /**
     * 分页查询
     * @Title: queryByCondition
     * @param: query      
     * @return: PageQuery 
     * @throws
     */
    public PageQuery<InterEmployeeJobDuty> queryByCondition(PageQuery query);
    
    /**
     * 不分页查询
     * @Title: queryByCondition
     * @param: interEmployeeJobDuty      
     * @return: List 
     * @throws
     */
    public List<InterEmployeeJobDuty> queryByCondition(InterEmployeeJobDuty interEmployeeJobDuty);
    
    /**
     * 根据id逻辑删除
     * @Title: queryByCondition
     * @param: interEmployeeJobDuty      
     * @return: int 
     * @throws
     */
    @SqlStatement(params = "id,updatedBy")
    public int delInterEmployeeJobDutyById(String id, String updatedBy);
    
    /**
     * 批量逻辑删除
     * @Title: batchDelAppUserByIds
     * @param: ids
     * @param: updatedBy
     * @return: int 
     * @throws
     */
    @SqlStatement(params = "ids,updatedBy")
    public int batchDelInterEmployeeJobDutyByIds(List<String> ids, String updatedBy);
}
  • InterEmployeeJobDutyService
package com.bossyang.beetsqlDemo.service;

import com.bossyang.beetsqlDemo.model.InterEmployeeJobDuty;
import org.beetl.sql.core.engine.PageQuery;

import java.util.List;

/**
 * InterEmployeeJobDuty Service
 * 
 * @author: 
 * @date: 2020年05月31日 16:24:13
 * @version V1.0
 */
public interface InterEmployeeJobDutyService {
    /**
     * 分页查询
     * @Title: queryByCondition
     * @param: pageNum 页码
     * @param: pageSize 每页数量
     * @param: InterEmployeeJobDuty 参数对象
     * @param: orderBy 排序方式
     * @return: pageQuery
     * @throws
     */
    public PageQuery<InterEmployeeJobDuty> queryByCondition(long pageNum, long pageSize, InterEmployeeJobDuty paramDto, String orderBy);
    
    /**
     * 根据条件查询,不分页
     * @Title: queryInterEmployeeJobDutyList
     * @param: interEmployeeJobDuty
     * @return: list
     * @throws
     */
    public List<InterEmployeeJobDuty> queryInterEmployeeJobDutyList(InterEmployeeJobDuty interEmployeeJobDuty);
    
    /**
     * 根据id查询InterEmployeeJobDuty
     * @Title: queryInterEmployeeJobDutyById  
     * @param: id      
     * @return: void 
     * @throws
     */
    public InterEmployeeJobDuty queryInterEmployeeJobDutyById(String id);
    
    /**
     * 保存InterEmployeeJobDuty
     * @Title: queryByCondition  
     * @param: interEmployeeJobDuty      
     * @throws
     */
    public void saveInterEmployeeJobDuty(InterEmployeeJobDuty interEmployeeJobDuty);
    
    /**
     * 更新InterEmployeeJobDuty
     * @Title: updateInterEmployeeJobDuty  
     * @param: interEmployeeJobDuty      
     * @return: boolean      
     * @throws
     */
    public boolean updateInterEmployeeJobDuty(InterEmployeeJobDuty interEmployeeJobDuty);
    
    /**
     * 逻辑删除InterEmployeeJobDuty
     * @Title: delInterEmployeeJobDutyById  
     * @param: id      
     * @param: updatedBy
     * @return: boolean 
     * @throws
     */
    public boolean delInterEmployeeJobDutyById(String id, String updatedBy);
    
    /**
     * 批量逻辑删除
     * @Title: batchDelInterEmployeeJobDutyByIds
     * @param: ids
     * @param: updatedBy
     * @return: boolean
     * @throws
     */
    public boolean batchDelInterEmployeeJobDutyByIds(List<String> ids, String updatedBy);
}
接口实现

在service下创建impl文件夹进行service的接口实现。

  • InterEmployeeServiceImpl
package com.bossyang.beetsqlDemo.service.impl;

import com.bossyang.beetsqlDemo.common.MyPageQuery;
import com.bossyang.beetsqlDemo.model.InterEmployee;
import com.bossyang.beetsqlDemo.repository.InterEmployeeDao;
import com.bossyang.beetsqlDemo.service.InterEmployeeService;
import org.beetl.sql.core.SQLManager;
import org.beetl.sql.core.engine.PageQuery;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.transaction.Transactional;
import java.util.Date;
import java.util.List;

/**
 * InterEmployee ServiceImpl
 *
 * @author:
 * @date: 2020年05月31日 13:43:25
 * @version V1.0
 */
@Service
public class InterEmployeeServiceImpl implements InterEmployeeService {
    @Autowired
    private SQLManager sqlManager;
    @Autowired
    private InterEmployeeDao interEmployeeDao;

    public PageQuery<InterEmployee> queryByCondition(long pageNum, long pageSize, InterEmployee paramDto, String orderBy) {
        PageQuery<InterEmployee> pageQuery = new PageQuery<InterEmployee>(pageNum, pageSize, paramDto);
        pageQuery.setOrderBy(orderBy);
        sqlManager.pageQuery("interEmployeeExtra.queryByCondition", InterEmployee.class, pageQuery);
        return pageQuery;
    }

    @Override
    public MyPageQuery<InterEmployee> querySummary(long pageNum, long pageSize, InterEmployee paramDto, String orderBy) {
        MyPageQuery<InterEmployee> myPageQuery = new MyPageQuery<>();
        PageQuery<InterEmployee> pageQuery = new PageQuery<InterEmployee>(pageNum, pageSize, paramDto);
        pageQuery.setOrderBy(orderBy);
        myPageQuery.setPageQuery(pageQuery);

        interEmployeeDao.pageQuery("queryByCondition", InterEmployee.class, myPageQuery);

        return myPageQuery;
    }

    public List<InterEmployee> queryInterEmployeeList(InterEmployee interEmployee) {
        return interEmployeeDao.queryByCondition(interEmployee);
    }

    public InterEmployee queryInterEmployeeById(String id) {
        return (InterEmployee) interEmployeeDao.single(id);
    }

    @Transactional
    public void saveInterEmployee(InterEmployee interEmployee) {
        if(StringUtils.isEmpty(interEmployee.getStatus())) {
            interEmployee.setStatus("1");
        }
        interEmployee.setCreatedTime(new Date());
        interEmployee.setUpdatedTime(new Date());
        interEmployeeDao.insert(interEmployee);
    }

    @Transactional
    public boolean updateInterEmployee(InterEmployee interEmployee) {
        interEmployee.setUpdatedTime(new Date());
        int result = interEmployeeDao.updateTemplateById(interEmployee);
        if(result > 0) {
            return true;
        } else {
            return false;
        }
    }

    @Transactional
    public boolean delInterEmployeeById(String id, String updatedBy) {
        int result = interEmployeeDao.delInterEmployeeById(id, updatedBy);
        if(result > 0) {
            return true;
        } else {
            return false;
        }
    }

    @Transactional
    public boolean batchDelInterEmployeeByIds(List<String> ids, String updatedBy){
        int result = interEmployeeDao.batchDelInterEmployeeByIds(ids, updatedBy);
        if(result > 0) {
            return true;
        } else {
            return false;
        }
    }
}
  • InterEmployeeJobDutyServiceImpl
package com.bossyang.beetsqlDemo.service.impl;

import com.bossyang.beetsqlDemo.model.InterEmployeeJobDuty;
import com.bossyang.beetsqlDemo.repository.InterEmployeeJobDutyDao;
import com.bossyang.beetsqlDemo.service.InterEmployeeJobDutyService;
import org.beetl.sql.core.SQLManager;
import org.beetl.sql.core.engine.PageQuery;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.util.List;

/**
 * InterEmployeeJobDuty ServiceImpl
 * 
 * @author: 
 * @date: 2020年05月31日 16:24:13
 * @version V1.0
 */
@Service
public class InterEmployeeJobDutyServiceImpl implements InterEmployeeJobDutyService {
    @Autowired
    private SQLManager sqlManager;
    @Autowired  
    private InterEmployeeJobDutyDao interEmployeeJobDutyDao;
    
    public PageQuery<InterEmployeeJobDuty> queryByCondition(long pageNum, long pageSize, InterEmployeeJobDuty paramDto, String orderBy) {
        PageQuery<InterEmployeeJobDuty> pageQuery = new PageQuery<InterEmployeeJobDuty>(pageNum, pageSize, paramDto);
        pageQuery.setOrderBy(orderBy);
        sqlManager.pageQuery("interEmployeeJobDutyExtra.queryByCondition", InterEmployeeJobDuty.class, pageQuery);
        return pageQuery;
    }
    
    public List<InterEmployeeJobDuty> queryInterEmployeeJobDutyList(InterEmployeeJobDuty interEmployeeJobDuty) {
        return interEmployeeJobDutyDao.queryByCondition(interEmployeeJobDuty);
    }
    
    public InterEmployeeJobDuty queryInterEmployeeJobDutyById(String id) {
        return interEmployeeJobDutyDao.single(id);
    }
    
    @Transactional
    public void saveInterEmployeeJobDuty(InterEmployeeJobDuty interEmployeeJobDuty) {

        interEmployeeJobDutyDao.insert(interEmployeeJobDuty);
    }
    
    @Transactional
    public boolean updateInterEmployeeJobDuty(InterEmployeeJobDuty interEmployeeJobDuty) {
        int result = interEmployeeJobDutyDao.updateTemplateById(interEmployeeJobDuty);
        if(result > 0) {
            return true;
        } else {
            return false;
        }
    }
    
    @Transactional
    public boolean delInterEmployeeJobDutyById(String id, String updatedBy) {
        int result = interEmployeeJobDutyDao.delInterEmployeeJobDutyById(id, updatedBy);
        if(result > 0) {
            return true;
        } else {
            return false;
        }
    }
    
    @Transactional
    public boolean batchDelInterEmployeeJobDutyByIds(List<String> ids, String updatedBy){
        int result = interEmployeeJobDutyDao.batchDelInterEmployeeJobDutyByIds(ids, updatedBy);
        if(result > 0) {
            return true;
        } else {
            return false;
        }
    }
}

ProtoBuffer

创建一个protobuf的文件夹,与resources、java平级

  • 创建protobuf文件夹

  • 创建protobuf文件

    • interEmployee.proto
    syntax = "proto3";
    package beetsqlDemo;
    option java_package = "com.bossyang.beetsqlDemo.proto.api";
    option java_outer_classname = "InterEmployeeProto";
    message InterEmployeeDto {
        string id = 1; //id
        string name = 2; //姓名
        string email = 3; //邮箱
        string sex = 4; //性别 0-男;1-女;2-未知
        string deptId = 5; //所属部门id
        string orgId = 6; //所属机构id
        string status = 7; //状态 0-无效;1-有效
        string createdBy = 8; //创建人
        string createdTime = 9; //创建时间
        string updatedBy = 10; //更新人
        string updatedTime = 11; //更新时间
    }
    message InterEmployeeQueryReq {
        int64 page = 1; //页码
        int64 size = 2; //每页数量
        string id = 3; //id
        string name = 4; //姓名
        string email = 5; //邮箱
        string sex = 6; //性别 0-男;1-女;2-未知
        string deptId = 7; //所属部门id
        string orgId = 8; //所属机构id
        string status = 9; //状态 0-无效;1-有效
        string createdBy = 10; //创建人
        string createdTime = 11; //创建时间
        string updatedBy = 12; //更新人
        string updatedTime = 13; //更新时间
        repeated string jobDuties = 14;//员工岗位集合
    }
    message InterEmployeeQueryRes {
        int64 total = 1; //总数
        int64 size = 2; //当前列表大小
        repeated InterEmployeeDto list=3;//列表
        int64 empNums = 4;//员工数量
    }
    
    • interEmployeeJobDuty.proto
syntax = "proto3";
package beetsqlDemo;
option java_package = "com.bossyang.beetsqlDemo.proto.api";
option java_outer_classname = "InterEmployeeJobDutyProto";
message InterEmployeeJobDutyDto {
    string id = 1; //id
    string empId = 2; //员工ID
    string empJob = 3; //员工岗位 CODE-开发;HR-人事经理
    string jobName = 4; //岗位名称 CODE-开发;HR-人事经理
}
message InterEmployeeJobDutyQueryReq {
    int64 page = 1; //页码
    int64 size = 2; //每页数量
    string id = 3; //id
    string empId = 4; //员工ID
    string empJob = 5; //员工岗位 CODE-开发;HR-人事经理
    string jobName = 6; //岗位名称 CODE-开发;HR-人事经理
}
message InterEmployeeJobDutyQueryRes {
    int64 total = 1; //总数
    int64 size = 2; //当前列表大小
    repeated InterEmployeeJobDutyDto list=3;//列表
}

业务实现

创建api文件夹进行业务实现以及前端交互

业务Service
业务接口定义

在api下面创建service文件夹,进行业务的接口定义

  • InterEmployeeApiService
package com.bossyang.beetsqlDemo.api.service;

import com.alibaba.fastjson.JSONObject;
import com.bossyang.beetsqlDemo.common.BaseResponse;
import com.bossyang.beetsqlDemo.model.InterEmployee;
import com.bossyang.beetsqlDemo.proto.api.InterEmployeeProto;
import com.google.protobuf.InvalidProtocolBufferException;

/**
 * InterEmployee ApiService
 * 解析&校验&组装
 * @author: 
 * @date: 2020年05月31日 13:43:25
 * @version V1.0
 */
public interface InterEmployeeApiService {
    /**
     * 根据条件查询
     * @param queryReqProto
     * @return
     */
    public BaseResponse<JSONObject> queryInterEmployees(InterEmployeeProto.InterEmployeeQueryReq queryReqProto) throws InvalidProtocolBufferException;
    /**
     * 根据条件查询
     * @param queryReqProto
     * @return
     */
    public BaseResponse<JSONObject> test(InterEmployeeProto.InterEmployeeQueryReq queryReqProto) throws InvalidProtocolBufferException;
    /**
     * 根据id查询
     * @param id
     * @return
     */
    public BaseResponse<JSONObject> queryInterEmployeeById(String id);
    /**
     * 保存或更新
     * @param interEmployee
     * @return
     */
    public BaseResponse<JSONObject> saveOrUpdateInterEmployee(InterEmployee interEmployee);
    /**
     * 根据id逻辑删除
     * @param id
     * @return
     */
    public BaseResponse<JSONObject> delInterEmployeeById(String id);
    /**
     * 根据id批量逻辑删除
     * @param ids
     * @return
     */
    public BaseResponse<JSONObject> batchDelInterEmployeeByIds(String ids);
}
业务接口实现

在service下面创建impl文件夹,进行业务接口的实现

  • InterEmployeeApiServiceImpl
package com.bossyang.beetsqlDemo.api.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.bossyang.beetsqlDemo.api.service.InterEmployeeApiService;
import com.bossyang.beetsqlDemo.common.BaseResponse;
import com.bossyang.beetsqlDemo.common.MyPageQuery;
import com.bossyang.beetsqlDemo.model.InterEmployee;
import com.bossyang.beetsqlDemo.proto.api.InterEmployeeProto;
import com.bossyang.beetsqlDemo.service.InterEmployeeService;
import com.bossyang.beetsqlDemo.util.UuidUtil;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.util.JsonFormat;
import org.beetl.sql.core.engine.PageQuery;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.Arrays;


/**
 * InterEmployee ApiServiceImpl
 * 
 * @author: 
 * @date: 2020年05月31日 16:28:46
 * @version V1.0
 */
@Component
public class InterEmployeeApiServiceImpl implements InterEmployeeApiService {
    private static JsonFormat.Parser parser = JsonFormat.parser().ignoringUnknownFields();
    private static JsonFormat.Printer printer = JsonFormat.printer().preservingProtoFieldNames().includingDefaultValueFields();
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    
    @Autowired
    private InterEmployeeService interEmployeeService;
    
    @Override
    public BaseResponse<JSONObject> queryInterEmployees(InterEmployeeProto.InterEmployeeQueryReq queryReqProto) throws InvalidProtocolBufferException {
        InterEmployee interEmployee = JSONObject.parseObject(JsonFormat.printer().print(queryReqProto), InterEmployee.class);
        String orderBy = " t.updated_time desc ";
        PageQuery<InterEmployee> pageQuery = interEmployeeService.queryByCondition(queryReqProto.getPage(), queryReqProto.getSize(), interEmployee, orderBy);
        InterEmployeeProto.InterEmployeeQueryRes.Builder interEmployeeRes = InterEmployeeProto.InterEmployeeQueryRes.newBuilder();
        pageQuery.getList().forEach(dto -> {
            try {
                InterEmployeeProto.InterEmployeeDto.Builder protoDto = InterEmployeeProto.InterEmployeeDto.newBuilder();
                parser.merge(JSONObject.toJSONStringWithDateFormat(dto, "yyyy-MM-dd HH:mm:ss"), protoDto);
                interEmployeeRes.addList(protoDto);
            } catch (InvalidProtocolBufferException e) {
                logger.error("查询结果转proto转换异常:", e);
                e.printStackTrace();
            }
        });
        interEmployeeRes.setSize(pageQuery.getPageSize());
        interEmployeeRes.setTotal(pageQuery.getTotalRow());
        return BaseResponse.setResultSuccess(JSONObject.parseObject(printer.print(interEmployeeRes.build())));
    }

    @Override
    public BaseResponse<JSONObject> test(InterEmployeeProto.InterEmployeeQueryReq queryReqProto) throws InvalidProtocolBufferException {
        InterEmployee interEmployee = JSONObject.parseObject(JsonFormat.printer().print(queryReqProto), InterEmployee.class);
        String orderBy = " t.updated_time desc ";
        MyPageQuery<InterEmployee> myPageQuery = interEmployeeService.querySummary(queryReqProto.getPage(), queryReqProto.getSize(), interEmployee, orderBy);
        InterEmployeeProto.InterEmployeeQueryRes.Builder interEmployeeRes = InterEmployeeProto.InterEmployeeQueryRes.newBuilder();
        PageQuery pageQuery = myPageQuery.getPageQuery();
        pageQuery.getList().forEach(dto -> {
            try {
                InterEmployeeProto.InterEmployeeDto.Builder protoDto = InterEmployeeProto.InterEmployeeDto.newBuilder();
                parser.merge(JSONObject.toJSONStringWithDateFormat(dto, "yyyy-MM-dd HH:mm:ss"), protoDto);
                interEmployeeRes.addList(protoDto);
            } catch (InvalidProtocolBufferException e) {
                logger.error("查询结果转proto转换异常:", e);
                e.printStackTrace();
            }
        });
        interEmployeeRes.setEmpNums(myPageQuery.getSummaryMap().get("empNums"));
        interEmployeeRes.setSize(pageQuery.getPageSize());
        interEmployeeRes.setTotal(pageQuery.getTotalRow());
        return BaseResponse.setResultSuccess(JSONObject.parseObject(printer.print(interEmployeeRes.build())));
    }

    @Override
    public BaseResponse<JSONObject> queryInterEmployeeById(String id) {
        InterEmployee interEmployee = interEmployeeService.queryInterEmployeeById(id);
        return BaseResponse.setResultSuccess(JSONObject.parseObject(JSON.toJSONStringWithDateFormat(interEmployee, "yyyy-MM-dd HH:mm:ss")));
    }
    
    @Override
    public BaseResponse<JSONObject> saveOrUpdateInterEmployee(InterEmployee interEmployee) {
        //校验参数
        if(StringUtils.isEmpty(interEmployee.getId())) {//保存
            interEmployee.setId(UuidUtil.getUUID32());
            interEmployee.setCreatedBy("admin");
            interEmployee.setOrgId(UuidUtil.getUUID32());
            interEmployeeService.saveInterEmployee(interEmployee);
        } else {//更新
            interEmployeeService.updateInterEmployee(interEmployee);
        }
        return BaseResponse.setResultSuccess(JSONObject.parseObject(JSON.toJSONStringWithDateFormat(interEmployee, "yyyy-MM-dd HH:mm:ss")));
    }
    
    @Override
    public BaseResponse<JSONObject> delInterEmployeeById(String id) {
        interEmployeeService.delInterEmployeeById(id,"admin");
        return BaseResponse.setResultSuccess();
    }
    
    @Override
    public BaseResponse<JSONObject> batchDelInterEmployeeByIds(String ids) {
        interEmployeeService.batchDelInterEmployeeByIds(Arrays.asList(ids.split(",")), "admin");
        return BaseResponse.setResultSuccess();
    }


}
Controller

在api文件夹下面创建controller文件夹

  • InterEmployeeController
package com.bossyang.beetsqlDemo.api.controller;

import com.alibaba.fastjson.JSONObject;
import com.bossyang.beetsqlDemo.api.service.InterEmployeeApiService;
import com.bossyang.beetsqlDemo.common.BaseResponse;
import com.bossyang.beetsqlDemo.model.InterEmployee;
import com.bossyang.beetsqlDemo.proto.api.InterEmployeeProto;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.util.JsonFormat;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
 * InterEmployee 接口
 * 
 * @author: 
 * @date: 2020年05月31日 16:28:46
 * @version V1.0
 */
@RestController
@RequestMapping("/api")
public class InterEmployeeController {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private static JsonFormat.Parser parser = JsonFormat.parser().ignoringUnknownFields();
    @Autowired
    private InterEmployeeApiService interEmployeeApiService;
    
    private static final String ENTITY_NAME = "interEmployee";

    /**
     * 查询机构员工信息
     */
    @PostMapping(value = "/interEmployee/list")
    public BaseResponse<JSONObject> queryInterEmployees(@RequestBody String reqJson) throws InvalidProtocolBufferException {
        if(StringUtils.isEmpty(reqJson)) {
            return BaseResponse.lossParam("reqJson");
        }
        InterEmployeeProto.InterEmployeeQueryReq.Builder queryReqProto = InterEmployeeProto.InterEmployeeQueryReq.newBuilder();
        try {
            parser.merge(reqJson, queryReqProto);
        } catch (InvalidProtocolBufferException e) {
            e.printStackTrace();
            logger.error("请求Json转换异常", e);
            return BaseResponse.setResultError("查询异常");
        }
        return interEmployeeApiService.queryInterEmployees(queryReqProto.build());
    }

    /**
     * 查询机构员工信息
     */
    @PostMapping(value = "/interEmployee/listTest")
    public BaseResponse<JSONObject> queryInterEmployeesTest(@RequestBody String reqJson) throws InvalidProtocolBufferException {
        if(StringUtils.isEmpty(reqJson)) {
            return BaseResponse.lossParam("reqJson");
        }
        InterEmployeeProto.InterEmployeeQueryReq.Builder queryReqProto = InterEmployeeProto.InterEmployeeQueryReq.newBuilder();
        try {
            parser.merge(reqJson, queryReqProto);
        } catch (InvalidProtocolBufferException e) {
            e.printStackTrace();
            logger.error("请求Json转换异常", e);
            return BaseResponse.setResultError("查询异常");
        }
        return interEmployeeApiService.test(queryReqProto.build());
    }

    @GetMapping(value = "/interEmployee/info/{id}")
    public BaseResponse<JSONObject> queryInterEmployeeById(@PathVariable String id) {
        return interEmployeeApiService.queryInterEmployeeById(id);
    }

    @PostMapping(value = "/interEmployee/save")
    public BaseResponse<JSONObject> saveInterEmployee(@RequestBody InterEmployee interEmployee) {
        return interEmployeeApiService.saveOrUpdateInterEmployee(interEmployee);
    }
    @PutMapping(value = "/interEmployee/update")
    public BaseResponse<JSONObject> updateInterEmployee(@RequestBody InterEmployee interEmployee) {
        return interEmployeeApiService.saveOrUpdateInterEmployee(interEmployee);
    }

    @DeleteMapping(value = "/interEmployee/del/{id}")
    public BaseResponse<JSONObject> delInterEmployee(@PathVariable String id) {
        return interEmployeeApiService.delInterEmployeeById(id);
    }
}

运行

启动运行application

测试

本案例采用DocwayAPI接口管理平台存储和定义接口,具体接口如下:

image-20201007194602045

  • 运行查询接口进行测试

image-20201007195402355

image-20201007195654916

响应结果:

image-20201007195736833

后台运行结果:

image-20201007195836747

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值