Project summary of grain College

Project summary of grain College

1. Project introduction

1.1 business model adopted

B2C mode (Business To Customer member mode)

Merchants to users, this mode is to make a large number of videos with their own copyright and put them on their own platform, so that users can pay monthly or annual fees. This model is simple and fast. It can develop rapidly as long as you concentrate on recording a large number of videos. It was hot because of lynda's sky high financing. However, in China, due to the weak awareness of copyright protection, easy reproduction of educational content and many competitors with massive free resources, it is difficult to obtain a decent cash flow

1.2 functional modules

Grain college is a B2C Online vocational skills education system, which is divided into foreground user system and background operation platform

1.3 adopted technology

2. Mybatis plus related configuration

2.1 configuring paging plug-ins

You can create a new mybatis plus configuration class MyBatisPlusConfig under the config package for unified management:

//Make it a configuration class
@Configuration
//Turn on transaction management
@EnableTransactionManagement
//Specify the package where the interface to become the implementation class is located, and then all interfaces under the package will generate corresponding implementation classes after compilation (the same as adding @ Mapper to each class)
@MapperScan("com.atguigu.eduservice.mapper")
public class MyBatisPlusConfig {
    //Configuring paging plug-ins
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2));
        return interceptor;
    }
}

2.2 automatic filling

Create a new MyMetaObjectHandler class to implement the MetaObjectHandler interface:

//Inject into spring
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
	//Auto fill on insertion
    @Override
    public void insertFill(MetaObject metaObject) {
        //Property name, not field name
        this.setFieldValByName("gmtCreate", new Date(), metaObject);
        this.setFieldValByName("gmtModified", new Date(), metaObject);
    }

	//Auto fill on update
    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("gmtModified", new Date(), metaObject);
    }
}

Annotate the fields that need to be automatically populated:

2.3 code generator

public class CodeGenerator {
    @Test
    public void run() {
        // 1. Create code generator
        AutoGenerator mpg = new AutoGenerator();

        // 2. Global configuration
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        //Project path
        gc.setOutputDir("D:\\guli_parent\\service\\service_edu" + "/src/main/java");

        gc.setAuthor("xppll");
        //Open Explorer after build
        gc.setOpen(false);
        //Whether the file is overwritten during regeneration
        gc.setFileOverride(false);

        //UserServie
        gc.setServiceName("%sService");    //Remove the initial I of the Service interface

        //Primary key policy
        gc.setIdType(IdType.ID_WORKER_STR);
        //Defines the date type in the generated entity class
        gc.setDateType(DateType.ONLY_DATE);
        //Turn on Swagger2 mode
        gc.setSwagger2(true);

        mpg.setGlobalConfig(gc);

        // 3. Data source configuration
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("root");
        dsc.setDbType(DbType.MYSQL);
        mpg.setDataSource(dsc);

        // 4. Package configuration
        PackageConfig pc = new PackageConfig();
        //Module name
        pc.setModuleName("eduservice");
        //Package com atguigu. eduservice
        pc.setParent("com.atguigu");
        //Package com atguigu. eduservice. controller
        pc.setController("controller");
        pc.setEntity("entity");
        pc.setService("service");
        pc.setMapper("mapper");
        mpg.setPackageInfo(pc);

        // 5. Policy configuration
        StrategyConfig strategy = new StrategyConfig();

        strategy.setInclude("edu_course", "edu_course_description", "edu_chapter", "edu_video");

        //Naming policy for mapping database tables to entities
        strategy.setNaming(NamingStrategy.underline_to_camel);
        //Remove table prefix when generating entities

        strategy.setTablePrefix(pc.getModuleName() + "_");
        //Naming policy for mapping database table fields to entities
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        // lombok model @ accessories (chain = true) setter chain operation
        strategy.setEntityLombokModel(true); 
        //restful api style controller
        strategy.setRestControllerStyle(true); 
        //Hump to hyphen in url
        strategy.setControllerMappingHyphenStyle(true); 
        mpg.setStrategy(strategy);
        
        // 6. Execute
        mpg.execute();
    }
}

3.Swagger configuration

Introduce Swagger related dependencies:

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <scope>provided </scope>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <scope>provided </scope>
</dependency

You can create a new Swagger configuration class SwaggerConfig under the config package for unified management:

/**
 * @author xppll
 * @date 2021/11/29 14:56
 */
@Configuration  //Configuration class
@EnableSwagger2 //swagger annotation
public class SwaggerConfig {
    @Bean
    public Docket webApiConfig() {
        return new Docket(DocumentationType.SWAGGER_2)
            .groupName("webApi")
            .apiInfo(webApiInfo())
            .select()
            .paths(Predicates.not(PathSelectors.regex("/admin/.*")))
            .paths(Predicates.not(PathSelectors.regex("/error.*")))
            .build();
    }

    private ApiInfo webApiInfo() {
        return new ApiInfoBuilder()
            .title("website-Course Center API file")
            .description("This document describes the microservice interface definition of the course center")
            .version("1.0")
            .contact(new Contact("Helen", "http://atguigu.com",
                                 "55317332@qq.com"))
            .build();
    }

}

Accessing the edu module, you can see:

4. Unified return data format

In the project, we will package the response into json return. Generally, we will unify the data format of all interfaces to make the data operation of the front end (IOS, Android, web) more consistent and easy. Generally, the unified return data format has no fixed format, as long as it can clearly describe the returned data status and the specific data to be returned. However, it usually includes status code, return message and data

4.1 unified result return class

Create a unified result return class R under the commonutils package

/**
 * Define classes that uniformly return results
 */
@Data
public class R {
    //Notes for swagger
    @ApiModelProperty(value = "Is it successful")
    private Boolean success;
    @ApiModelProperty(value = "Return code")
    private Integer code;
    @ApiModelProperty(value = "Return message")
    private String message;
    @ApiModelProperty(value = "Return data")
    private Map<String, Object> data = new HashMap<String, Object>();

    //Construction method private
    public R() {
    }

    //Successful static method
    public static R ok() {
        R r = new R();
        r.setSuccess(true);
        r.setCode(ResultCode.SUCCESS);
        r.setMessage("success");
        return r;
    }
    //Failed static method
    public static R error(){
        R r = new R();
        r.setSuccess(false);
        r.setCode(ResultCode.ERROR);
        r.setMessage("fail");
        return r;
    }

    //this is returned for chain programming, such as r.ok() code(). message()
    public R success(Boolean success) {
        this.setSuccess(success);
        return this;
    }

    public R message(String message) {
        this.setMessage(message);
        return this;
    }

    public R code(Integer code) {
        this.setCode(code);
        return this;
    }

    public R data(String key, Object value) {
        this.data.put(key, value);
        return this;
    }

    public R data(Map<String, Object> map) {
        this.setData(map);
        return this;
    }

}

4.2 unified definition of return code

There are many ways here. Here are two:

1. Create interface definition return code

public interface ResultCode {
    public static Integer SUCCESS = 20000;
    public static Integer ERROR = 20001;
}

2. Create an enumeration class to define the return code

public enum ErrorCode {

    PARAMS_ERROR(10001, "Parameter error"),
    ACCOUNT_PWD_NOT_EXIST(10002, "User name or password does not exist"),
    TOKEN_ERROR(10003, "token wrongful"),
    ACCOUNT_EXIST(10004, "Account already exists"),
    NO_PERMISSION(70001, "No access"),
    SESSION_TIME_OUT(90001, "session time out"),
    NO_LOGIN(90002, "Not logged in");

    private int code;
    private String msg;

    ErrorCode(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }
    //get,set method
}

5. Unified exception handling

5.1 create a unified exception handler

Create a unified exception handling class GlobalExceptionHandler under the handler package:

/**
 * Unified exception handling class
 *
 * @author xppll
 * @date 2021/11/29 19:11
 */
//Intercept the method with @ Controller, and the implementation of AOP
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    //Process once to handle exception Class exception
    @ExceptionHandler(Exception.class)
    //Return json data and directly return to the page without adding
    @ResponseBody
    public R error(Exception e) {
        e.printStackTrace();
        //Write the information to the log file
        log.error(e.getMessage());
        return R.error().message("Global exception handling was performed...");
    }

}

You can also handle specific exceptions:

//Add a specific exception method
@ExceptionHandler(ArithmeticException.class)
@ResponseBody
public R error(ArithmeticException e){
    e.printStackTrace();
    return R.error().message("A specific exception was executed");
}

5.2 custom exception handling

Create a custom exception class GuliException under the handler package:

/**
 * Custom exception
 * Need to inherit RuntimeException
 * @author xppll
 * @date 2021/11/29 20:09
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class GuliException extends RuntimeException {
    //Status code
    private Integer code;
    //Abnormal information
    private String msg;
}

Handling custom exceptions:

//Add custom exception
//You need to throw it manually
@ExceptionHandler(GuliException.class)
@ResponseBody
public R error(GuliException e){
    log.error(e.getMessage());
    e.printStackTrace();
    //Pass in self-defined parameters
    return R.error().code(e.getCode()).message(e.getMsg());
}

Chestnuts: throw them manually

@GetMapping("findAll")
public R list(){
    try {
        int a = 10/0;
    }catch(Exception e) {
        throw new GuliException(20003,"Custom exception occurred");
    }
    List<EduTeacher> list = teacherService.list(null);
    return R.ok().data("items",list);
}

6. Unified log processing

6.1 configuring log levels

The behavior of the Logger is hierarchical. As shown in the following table: OFF, fat, ERROR, WARN, INFO, DEBUG and ALL. By default, the log level printed by spring boot from the console is only INFO or above. The log level can be configured:

# Set log level
logging.level.root=WARN

This configuration method can only print logs on the console

6.2 Logback log

spring boot uses Logback internally as the framework for log implementation

Configure logback logs

Note: you need to delete application Other log configurations in properties

Create logback spring. Net in resources XML (the name as like as two peas!)

<?xml version="1.0" encoding="UTF-8"?>
<configuration  scan="true" scanPeriod="10 seconds">
    <!-- The log level is from low to high TRACE < DEBUG < INFO < WARN < ERROR < FATAL,If set to WARN,Is lower than WARN No information will be output -->
    <!-- scan:When this property is set to true If the configuration file changes, it will be reloaded. The default value is true -->
    <!-- scanPeriod:Set the time interval for monitoring whether the configuration file is modified. If no time unit is given, the default unit is milliseconds. When scan by true This property takes effect when. The default interval is 1 minute. -->
    <!-- debug:When this property is set to true When, it will be printed out logback Internal log information, real-time viewing logback Running status. The default value is false.  -->

    <contextName>logback</contextName>
    <!-- name The value of is the name of the variable, value The value defined by the variable. Values defined by are inserted into the logger In context. After defining variables, you can make“ ${}"To use variables. -->
    <property name="log.path" value="D:/guli_1010/edu" />

    <!-- Color log -->
    <!-- Configure format variables: CONSOLE_LOG_PATTERN Color log format -->
    <!-- magenta:Magenta -->
    <!-- boldMagenta:Coarse red-->
    <!-- cyan:Cyan -->
    <!-- white:white -->
    <!-- magenta:Magenta -->
    <property name="CONSOLE_LOG_PATTERN"
              value="%yellow(%date{yyyy-MM-dd HH:mm:ss}) |%highlight(%-5level) |%blue(%thread) |%blue(%file:%line) |%green(%logger) |%cyan(%msg%n)"/>


    <!--Output to console-->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!--This log appender It is used for development. Only the lowest level is configured. The log level output from the console is log information greater than or equal to this level-->
        <!-- For example, if you configure INFO Level, even if other locations are configured DEBUG Level logs will not be output -->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
        <encoder>
            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
            <!-- Set character set -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>


    <!--output to a file-->

    <!-- Time scrolling output level by INFO journal -->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- The path and file name of the log file being recorded -->
        <file>${log.path}/log_info.log</file>
        <!--Log file output format-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!-- The rolling strategy of the logger, recording by date and by size -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- Daily log archive path and format -->
            <fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--Log file retention days-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- This log file only records info Rank -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- Time scrolling output level by WARN journal -->
    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- The path and file name of the log file being recorded -->
        <file>${log.path}/log_warn.log</file>
        <!--Log file output format-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- Set character set here -->
        </encoder>
        <!-- The rolling strategy of the logger, recording by date and by size -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--Log file retention days-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- This log file only records warn Rank -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>warn</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>


    <!-- Time scrolling output level by ERROR journal -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- The path and file name of the log file being recorded -->
        <file>${log.path}/log_error.log</file>
        <!--Log file output format-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- Set character set here -->
        </encoder>
        <!-- The rolling strategy of the logger, recording by date and by size -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--Log file retention days-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- This log file only records ERROR Rank -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!--
        <logger>Used to set the log printing level of a package or a specific class, and specify<appender>. 
        <logger>Only one name Properties,
        An optional level And an optional addtivity Properties.
        name:Used to specify that this logger A package or a specific class of constraints.
        level:Used to set the print level, regardless of case: TRACE, DEBUG, INFO, WARN, ERROR, ALL and OFF,
              If this property is not set, the current logger Will inherit the level of the superior.
    -->
    <!--
        use mybatis When I was young, sql Statement is debug It will not print until next, and here we only configure it info,So I want to see sql Statement, there are two operations:
        First handle<root level="INFO">Change to<root level="DEBUG">This will print sql,But there will be a lot of other messages in the log
        The second is to give it alone mapper Directory configuration under DEBUG Mode, the code is as follows, which is configured in this way sql The statement will print, and others are normal DEBUG Level:
     -->
    <!--development environment :Print Console -->
    <springProfile name="dev">
        <!--You can output data in a project debug Logs, including mybatis of sql journal-->
        <logger name="com.guli" level="INFO" />

        <!--
            root Node is a required node. It is used to specify the most basic log output level. There is only one node level attribute
            level:Used to set the printing level, regardless of case: TRACE, DEBUG, INFO, WARN, ERROR, ALL and OFF,The default is DEBUG
            Can contain zero or more appender Element.
        -->
        <root level="INFO">
            <appender-ref ref="CONSOLE" />
            <appender-ref ref="INFO_FILE" />
            <appender-ref ref="WARN_FILE" />
            <appender-ref ref="ERROR_FILE" />
        </root>
    </springProfile>


    <!--production environment :output to a file-->
    <springProfile name="pro">

        <root level="INFO">
            <appender-ref ref="CONSOLE" />
            <appender-ref ref="DEBUG_FILE" />
            <appender-ref ref="INFO_FILE" />
            <appender-ref ref="ERROR_FILE" />
            <appender-ref ref="WARN_FILE" />
        </root>
    </springProfile>

</configuration>

6.3 output error log to file

for instance:

  1. Add annotation on class in GlobalExceptionHandler @Slf4j
  2. Exception output statement: log error(e.getMessage());

7. Integrate Alibaba cloud OSS

SpringBoot integrates alicloud OSS

8. Integrate EasyExcel

SpringBoot integrates EasyExcel

9. Integrate Alibaba cloud video on demand

SpringBoot integrates Alibaba cloud video on demand

10. Integrate JWT single sign on

For detailed knowledge of JWT, please refer to: JWT integrates Springboot

10.1 single sign on

There are three common ways of single sign on:

  1. Implementation of session broadcast mechanism
  2. Implementation using cookie+reids
  3. Using token to implement

10.2 introducing dependencies

<dependencies>
    <!-- JWT-->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
    </dependency>
</dependencies>

10.3 create JWT tool class

/**
 * @author xppll
 * @date 2021/12/8 13:49
 */
public class JwtUtils {

    //token expiration time
    public static final long EXPIRE = 1000 * 60 * 60 * 24;
    //Secret key
    public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";

    /**
     * Get token
     *
     * @param id       User id
     * @param nickname User nickname
     * @return
     */
    public static String getJwtToken(String id, String nickname) {

        String JwtToken = Jwts.builder()
            //Set jwt header information
            .setHeaderParam("typ", "JWT")
            .setHeaderParam("alg", "HS256")
            //Set classification
            .setSubject("guli-user")
            //Set issuing time
            .setIssuedAt(new Date())
            //Set expiration time = current time + expiration time after how long
            .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
            //Set the token body part to store user information
            .claim("id", id)
            .claim("nickname", nickname)
            //Set issuing algorithm + secret key
            .signWith(SignatureAlgorithm.HS256, APP_SECRET)
            .compact();
        return JwtToken;
    }

    /**
     * Judge whether the token exists and is valid
     *
     * @param jwtToken
     * @return
     */
    public static boolean checkToken(String jwtToken) {
        if (StringUtils.isEmpty(jwtToken)) return false;
        try {
            //Verify whether the token is a valid token
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * Judge whether the token exists and is valid
     *
     * @param request
     * @return
     */
    public static boolean checkToken(HttpServletRequest request) {
        try {
            String jwtToken = request.getHeader("token");
            if (StringUtils.isEmpty(jwtToken)) return false;
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * Get the user id according to the token
     *
     * @param request
     * @return
     */
    public static String getMemberIdByJwtToken(HttpServletRequest request) {
        String jwtToken = request.getHeader("token");
        if (StringUtils.isEmpty(jwtToken)) return "";
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        Claims claims = claimsJws.getBody();
        return (String) claims.get("id");
    }
}

10.4 information received and transmitted by the packaging front end

Login information:

@Data
public class UcentMemberVo {
    @ApiModelProperty(value = "cell-phone number")
    private String mobile;

    @ApiModelProperty(value = "password")
    private String password;
}

Registration information:

@Data
public class RegisterVo {
    private String nickname;
    private String mobile;
    private String password;
    private String code;
}

10.5 controller layer

There are three main interfaces:

  1. Sign in
  2. register
  3. After successful login, the user information is obtained according to the token for front-end display
/**
 * Membership table front controller
 *
 * @author xppll
 * @since 2021-12-08
 */
@CrossOrigin
@RestController
@RequestMapping("/educenter/member")
public class UcenterMemberController {

    @Autowired
    private UcenterMemberService memberService;

    /**
     * Sign in
     *
     * @param member Receive data from front-end login
     * @return Return to R
     */
    @PostMapping("login")
    public R loginUser(@RequestBody UcentMemberVo member) {
        //Return the token and generate it using jwt
        String token = memberService.login(member);
        return R.ok().data("token", token);
    }


    /**
     * register
     *
     * @param registerVo Receive data from front-end registration
     * @return Return to R
     */
    @PostMapping("register")
    public R registerUser(@RequestBody RegisterVo registerVo) {
        memberService.register(registerVo);
        return R.ok();
    }

    /**
     * Obtain user information according to the token for front-end display
     *
     * @param request
     * @return
     */
    @GetMapping("getMemberInfo")
    public R getMemberInfo(HttpServletRequest request) {
        //Call the jwt tool class, get the header information according to the request object, and return the user id
        String memberId = JwtUtils.getMemberIdByJwtToken(request);
        UcentMemberVo member = memberService.getLoginInfo(memberId);
        return R.ok().data("userInfo", member);
    }
}

10.6 service layer

/**
 * Membership table service implementation class
 *
 * @author xppll
 * @since 2021-12-08
 */
@Service
public class UcenterMemberServiceImpl extends ServiceImpl<UcenterMemberMapper, UcenterMember> implements UcenterMemberService {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     * Sign in
     *
     * @param member Parameters transmitted by the front end
     * @return Return token
     */
    @Override
    public String login(UcentMemberVo member) {
        //Get login phone number and password
        String mobile = member.getMobile();
        String password = member.getPassword();
        //1. One of the two is empty, login failed!
        if (StringUtils.isBlank(mobile) || StringUtils.isBlank(password)) {
            throw new GuliException(20001, "Mobile number and password cannot be empty, login failed!");
        }
        //2. Judge whether the mobile phone number exists
        LambdaQueryWrapper<UcenterMember> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UcenterMember::getMobile, mobile);
        UcenterMember mobileMember = baseMapper.selectOne(queryWrapper);
        if (mobileMember == null) {
            throw new GuliException(20001, "Mobile number does not exist, login failed!");
        }
        //3. Judge whether the password is correct
        //The database password is encrypted
        //The input password needs to be encrypted in comparison
        if (!MD5.encrypt(password).equals(mobileMember.getPassword())) {
            throw new GuliException(20001, "Password error, login failed!");
        }

        //4. Judge whether the user is banned (seal number)
        if (mobileMember.getIsDisabled()) {
            throw new GuliException(20001, "User has been forbidden to log in, login failed!");
        }
        //Call JWT tool class to generate token
        //Pass in id, nickname
        return JwtUtils.getJwtToken(mobileMember.getId(), mobileMember.getNickname());
    }

    //register
    @Override
    public void register(RegisterVo registerVo) {
        //Verification Code
        String code = registerVo.getCode();
        //cell-phone number
        String mobile = registerVo.getMobile();
        //nickname
        String nickname = registerVo.getNickname();
        //password
        String password = registerVo.getPassword();
        if (StringUtils.isBlank(mobile) || StringUtils.isBlank(code)
            || StringUtils.isBlank(nickname) || StringUtils.isBlank(password)) {
            throw new GuliException(20001, "The passed in parameter cannot be empty!, login has failed");
        }
        //Retrieve the verification code from redis
        String redisCode = redisTemplate.opsForValue().get(mobile);
		//Judge whether the verification code is invalid
        if (StringUtils.isBlank(redisCode)) {
            throw new GuliException(20001, "Verification code is invalid!, login has failed");
        }
        //Determine whether the verification code is correct
        if (!code.equals(redisCode)) {
            throw new GuliException(20001, "Verification code error!,login has failed");
        }
        //Judge whether the mobile phone number has been registered
        LambdaQueryWrapper<UcenterMember> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UcenterMember::getMobile, mobile);
        Integer count = baseMapper.selectCount(queryWrapper);
        if (count > 0) {
            throw new GuliException(20001, "The mobile phone number has been registered! login has failed");
        }
        //Add to database
        UcenterMember member = new UcenterMember();
        member.setMobile(mobile);
        member.setNickname(nickname);
        //The password needs to be encrypted
        member.setPassword(MD5.encrypt(password));
        member.setIsDisabled(false);
        member.setAvatar("https://xppll.oss-cn-beijing.aliyuncs.com/2021/12/08/dde5b98fe9dca6b6076file.png");

        baseMapper.insert(member);
    }

    //Get the information according to the id and send it to the front end
    @Override
    public UcentMemberVo getLoginInfo(String memberId) {
        UcenterMember member = baseMapper.selectById(memberId);
        UcentMemberVo ucentMemberVo = new UcentMemberVo();
        BeanUtils.copyProperties(member, ucentMemberVo);
        return ucentMemberVo;
    }

}

11. Integrate Alibaba cloud SMS

The SMS function is implemented here in order to complete user registration

11.1 preparation

First, you need to open Alibaba cloud SMS service

When importing dependencies:

<dependencies>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
    </dependency>
    <dependency>
        <groupId>com.aliyun</groupId>
        <artifactId>aliyun-java-sdk-core</artifactId>
    </dependency>
</dependencies>

11.2 specific implementation

controller layer:

/**
 * @author xppll
 * @date 2021/12/8 19:52
 */
@RestController
@RequestMapping("/edumsm/msm")
public class MsmController {
    @Autowired
    private MsmService msmService;

    @Autowired
    private RedisTemplate<String, String> reditemplate;

    //Method of sending short message through mobile phone number
    @GetMapping("send/{phone}")
    public R sendMsm(@PathVariable("phone") String phone) {
        //1. Obtain the verification code from redis. If it is obtained, it will be returned directly
        String code = reditemplate.opsForValue().get(phone);
        if (!StringUtils.isEmpty(code)) {
            return R.ok();
        }

        //2. Call the tool class to generate a four digit random number and pass it to Alibaba cloud for sending
        code = RandomUtil.getFourBitRandom();
        Map<String, Object> param = new HashMap<>();
        param.put("code", code);
        //3. Call the method in the service to send SMS
        boolean isSend = msmService.send(param, phone);
        if (isSend) {
            //4. After successful sending, put the successfully sent verification code into redis and set the effective time
            reditemplate.opsForValue().set(phone, code, 5, TimeUnit.MINUTES);
            return R.ok();
        } else {
            //5. Send failed, return failure information
            return R.error().message("SMS sending failed!");
        }
    }
}

service layer:

/**
 * @author xppll
 * @date 2021/12/8 19:53
 */
@Service
public class MsmServiceImpl implements MsmService {
    /**
     * Send SMS
     * @param param The verification code sent by alicloud is required
     * @param phone cell-phone number
     * @return
     */
    @Override
    public boolean send(Map<String, Object> param, String phone) {

        //If the mobile phone number is empty, false is returned
        if (StringUtils.isEmpty(phone)) return false;
        //Region node, id, key
        DefaultProfile profile =
            DefaultProfile.getProfile("default", "xxx", "xxx");
        IAcsClient client = new DefaultAcsClient(profile);

        //Set relevant parameters
        CommonRequest request = new CommonRequest();
        request.setSysMethod(MethodType.POST);
        request.setSysDomain("dysmsapi.aliyuncs.com");
        request.setSysVersion("2017-05-25");
        request.setSysAction("SendSms");
        //Set sending related parameters
        //cell-phone number
        request.putQueryParameter("PhoneNumbers", phone);
        //"Signature name" applied in Alibaba cloud“
        request.putQueryParameter("SignName", "My grain online education website");
        //"Template CODE" applied by Alibaba cloud
        request.putQueryParameter("TemplateCode", "SMS_xxxxx");
        //Verification Code
        request.putQueryParameter("TemplateParam", JSONObject.toJSONString(param));

        //send out
        try {
            CommonResponse response = client.getCommonResponse(request);
            System.out.println(response.getData());
            return response.getHttpResponse().isSuccess();
        } catch (ClientException e) {
            e.printStackTrace();
        }
        return false;
    }
}

12. Integrate wechat scanning login

SpringBoot integrates wechat login

13. Regularly count the number of registered people every day

13.1 database tables and entity classes

Database table statistics_daily:

Corresponding entity class:

/**
 * Website statistics daily data
 *
 * @author xppll
 * @since 2021-12-16
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="StatisticsDaily object", description="Website statistics daily data")
public class StatisticsDaily implements Serializable {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "Primary key")
    @TableId(value = "id", type = IdType.ID_WORKER_STR)
    private String id;

    @ApiModelProperty(value = "Statistical date")
    private String dateCalculated;

    @ApiModelProperty(value = "Number of registrants")
    private Integer registerNum;

    @ApiModelProperty(value = "Number of login")
    private Integer loginNum;

    @ApiModelProperty(value = "Number of videos played per day")
    private Integer videoViewNum;

    @ApiModelProperty(value = "Number of new courses per day")
    private Integer courseNum;

    @ApiModelProperty(value = "Creation time")
    @TableField(fill = FieldFill.INSERT)
    private Date gmtCreate;

    @ApiModelProperty(value = "Update time")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date gmtModified;

}

13.2 implementation interface

In service_ The UCenter module creates an interface to count the number of registered people on a certain day:

controller layer:

//Query the number of registrants on a certain day
@GetMapping("countRegister/{day}")
public R countRegister(@PathVariable("day") String day){
    Integer count=memberService.countRegisterDay(day);
    return R.ok().data("countRegister",count);
}

service layer:

@Override
public Integer countRegisterDay(String day) {
    return baseMapper.countRegisterDay(day);
}

mapper layer:

<!--Query the number of registrants on a certain day-->
<select id="countRegisterDay" resultType="java.lang.Integer">
    SELECT COUNT(*)
    FROM ucenter_member uc
    WHERE DATE(uc.gmt_create) = #{day}
</select>

13.3 remote call

In service_ The statistics module creates a remote call interface:

Under the client package, the UcenterClient interface:

/**
 * @author xppll
 * @date 2021/12/16 22:38
 */
@Component
@FeignClient("service-ucenter")
public interface UcenterClient {
    //Query the number of registrants on a certain day
    @GetMapping("/educenter/member/countRegister/{day}")
    public R countRegister(@PathVariable("day") String day);
}

controller layer:

//Count the number of registrants on a certain day and generate statistics
@PostMapping("registerCount/{day}")
public R registerCount(@PathVariable("day") String day){
    staService.registerCount(day);
    return R.ok();
}

service layer:

@Service
public class StatisticsDailyServiceImpl extends ServiceImpl<StatisticsDailyMapper, StatisticsDaily> implements StatisticsDailyService {
    @Autowired
    private UcenterClient ucenterClient;

    @Override
    public void registerCount(String day) {
        //First delete the database records of that day, and then rewrite the addition to prevent adding multiple records
        LambdaQueryWrapper<StatisticsDaily> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(StatisticsDaily::getDateCalculated, day);
        baseMapper.delete(queryWrapper);
        //Remote call to get the number of registrants on a certain day
        R register = ucenterClient.countRegister(day);
        Integer count = (Integer) register.getData().get("countRegister");
        //Add the obtained data to the database and statistical analysis table
        StatisticsDaily sta = new StatisticsDaily();
        //Number of registrants
        sta.setRegisterNum(count);
        //Statistical date
        sta.setDateCalculated(day);
        //Daily video playback
        sta.setVideoViewNum(RandomUtils.nextInt(100, 200));
        //Daily login number
        sta.setLoginNum(RandomUtils.nextInt(100, 200));
        //Number of new courses per day
        sta.setCourseNum(RandomUtils.nextInt(100, 200));
        baseMapper.insert(sta);
    }
}

13.4 scheduled tasks

Recommend a website that can generate cron expressions for the required scheduled tasks: Online Cron expression builder (qqe2.com)

Create a scheduled task class using cron expression:

/**
 * @author xppll
 * @date 2021/12/17 13:31
 */
@Component
public class ScheduledTask {

    @Autowired
    private StatisticsDailyService staService;

    /**
     * At 1 a.m. every day, query and add the data of the previous day
     */
    @Scheduled(cron = "0 0 1 * * ?")
    public void task(){
        staService.registerCount(DateUtil.formatDate(DateUtil.addDays(new Date(),-1)));
    }
}

The date conversion tool class DateUtil here:

/**
 * Date operation tool class
 *
 * @author qy
 * @since 1.0
 */
public class DateUtil {

    private static final String dateFormat = "yyyy-MM-dd";

    /**
     * format date
     *
     * @param date
     * @return
     */
    public static String formatDate(Date date) {
        SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
        return sdf.format(date);

    }

    /**
     * Add amount days to date.
     *
     * @param date   Date processed, non null
     * @param amount The number of days to add may be negative
     */
    public static Date addDays(Date date, int amount) {
        Calendar now = Calendar.getInstance();
        now.setTime(date);
        now.set(Calendar.DATE, now.get(Calendar.DATE) + amount);
        return now.getTime();
    }

    public static void main(String[] args) {
        System.out.println(DateUtil.formatDate(new Date()));
        System.out.println(DateUtil.formatDate(DateUtil.addDays(new Date(), -1)));
    }
}

14. Integrate wechat payment

SpringBoot integrates wechat payment

15. Authority management module

To be added

16. gateway

For details on the use of gateway in microservices, please refer to: [spring cloud] learning notes -p4 (Gateway service Gateway)

16.1 preparation

Create an API gateway module (Gateway Service):

Introduce related dependencies:

<dependencies>
    <dependency>
        <groupId>com.atguigu</groupId>
        <artifactId>common_utils</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>

    <!--gson-->
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
    </dependency>

    <!--Service call-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
</dependencies>

16.2 writing basic configuration and routing rules

# Service port
server.port=8222
# service name
spring.application.name=service-gateway
# nacos service address
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
#Discover routes using services
spring.cloud.gateway.discovery.locator.enabled=true
#Service route name lowercase
#spring.cloud.gateway.discovery.locator.lower-case-service-id=true
#Routing id, user-defined, as long as it is unique
spring.cloud.gateway.routes[0].id=service-acl
#The destination address lb of the route is load balancing, followed by the service name
spring.cloud.gateway.routes[0].uri=lb://service-acl
#Routing assertion is to judge whether the request meets the conditions of routing rules
spring.cloud.gateway.routes[0].predicates= Path=/*/acl/**
#Configure service edu service
spring.cloud.gateway.routes[1].id=service-edu
spring.cloud.gateway.routes[1].uri=lb://service-edu
spring.cloud.gateway.routes[1].predicates= Path=/eduservice/**
#Configuring the service UCenter service
spring.cloud.gateway.routes[2].id=service-ucenter
spring.cloud.gateway.routes[2].uri=lb://service-ucenter
spring.cloud.gateway.routes[2].predicates= Path=/ucenterservice/**
#Configuring the service UCenter service
spring.cloud.gateway.routes[3].id=service-cms
spring.cloud.gateway.routes[3].uri=lb://service-cms
spring.cloud.gateway.routes[3].predicates= Path=/cmsservice/**
spring.cloud.gateway.routes[4].id=service-msm
spring.cloud.gateway.routes[4].uri=lb://service-msm
spring.cloud.gateway.routes[4].predicates= Path=/edumsm/**
spring.cloud.gateway.routes[5].id=service-order
spring.cloud.gateway.routes[5].uri=lb://service-order
spring.cloud.gateway.routes[5].predicates= Path=/orderservice/**
spring.cloud.gateway.routes[6].id=service-order
spring.cloud.gateway.routes[6].uri=lb://service-order
spring.cloud.gateway.routes[6].predicates= Path=/orderservice/**
spring.cloud.gateway.routes[7].id=service-oss
spring.cloud.gateway.routes[7].uri=lb://service-oss
spring.cloud.gateway.routes[7].predicates= Path=/eduoss/**
spring.cloud.gateway.routes[8].id=service-statistic
spring.cloud.gateway.routes[8].uri=lb://service-statistic
spring.cloud.gateway.routes[8].predicates= Path=/staservice/**
spring.cloud.gateway.routes[9].id=service-vod
spring.cloud.gateway.routes[9].uri=lb://service-vod
spring.cloud.gateway.routes[9].predicates= Path=/eduvod/**
spring.cloud.gateway.routes[10].id=service-edu
spring.cloud.gateway.routes[10].uri=lb://service-edu
spring.cloud.gateway.routes[10].predicates= Path=/eduuser/**


spring.redis.host=192.168.75.130

spring.redis.port=6379
spring.redis.database= 0
spring.redis.timeout=1800000
spring.redis.lettuce.pool.max-active=20
spring.redis.lettuce.pool.max-wait=-1
#Maximum blocking waiting time (negative number means no limit)
spring.redis.lettuce.pool.max-idle=5
spring.redis.lettuce.pool.min-idle=0

16.3 gateway to solve cross domain problems

Here, we use the gateway to solve the cross domain problem, so we don't need to use nginx+@CrossOrigin to solve the cross domain problem:

@Configuration
public class CorsConfig {
    @Bean
    public CorsWebFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        //Allowed cross domain ajax request mode
        config.addAllowedMethod("*");
        //Which websites are allowed to cross domain requests, here * is all
        config.addAllowedOrigin("*");
        //Header allowed in request
        config.addAllowedHeader("*");

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
        source.registerCorsConfiguration("/**", config);

        return new CorsWebFilter(source);
    }
}

16.4 Filter usage

To create a global Filter class, you need to implement the GlobalFilter interface to uniformly handle member login and external services that are not allowed to access:

/**
 * Global Filter, which uniformly handles member login and external services that are not allowed to access
 *
 * @author qy
 * @since 2019-11-21
 */
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //Get request parameters
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        //The api interface of grain college, the verification user must log in
        if (antPathMatcher.match("/api/**/auth/**", path)) {
            List<String> tokenList = request.getHeaders().get("token");
            //token Null, not logged in, blocking request
            if (null == tokenList) {
                ServerHttpResponse response = exchange.getResponse();
                return out(response);
            } else {
                //Intercept request
                ServerHttpResponse response = exchange.getResponse();
                return out(response);
            }
        }
        //Internal service interface, external access is not allowed
        if (antPathMatcher.match("/**/inner/**", path)) {
            //Intercept request
            ServerHttpResponse response = exchange.getResponse();
            return out(response);
        }
        //Release
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }

    private Mono<Void> out(ServerHttpResponse response) {
        JsonObject message = new JsonObject();
        message.addProperty("success", false);
        message.addProperty("code", 28004);
        message.addProperty("data", "Authentication failed");
        byte[] bits = message.toString().getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        //response.setStatusCode(HttpStatus.UNAUTHORIZED);
        //Specify the code, otherwise Chinese garbled code will appear in the browser
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }
}

16.5 custom exception handling

When the service gateway calls the service, there may be some exceptions or the service is unavailable. The error information returned by the service gateway is unfriendly. We need to override the processing and create an exception handling class ErrorHandlerConfig:

@Configuration
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
public class ErrorHandlerConfig {

    private final ServerProperties serverProperties;

    private final ApplicationContext applicationContext;

    private final ResourceProperties resourceProperties;

    private final List<ViewResolver> viewResolvers;

    private final ServerCodecConfigurer serverCodecConfigurer;

    public ErrorHandlerConfig(ServerProperties serverProperties,
                              ResourceProperties resourceProperties,
                              ObjectProvider<List<ViewResolver>> viewResolversProvider,
                              ServerCodecConfigurer serverCodecConfigurer,
                              ApplicationContext applicationContext) {
        this.serverProperties = serverProperties;
        this.applicationContext = applicationContext;
        this.resourceProperties = resourceProperties;
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
        JsonExceptionHandler exceptionHandler = new JsonExceptionHandler(
            errorAttributes,
            this.resourceProperties,
            this.serverProperties.getError(),
            this.applicationContext);
        exceptionHandler.setViewResolvers(this.viewResolvers);
        exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
        exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
        return exceptionHandler;
    }
}

JsonExceptionHandler:

/**
 * Custom exception handling
 *
 * <p>In case of exception, JSON is used instead of HTML exception information < p >
/
public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {

    public JsonExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties,
                                ErrorProperties errorProperties, ApplicationContext applicationContext) {
        super(errorAttributes, resourceProperties, errorProperties, applicationContext);
    }

    /**
     * Get exception properties
     */
    @Override
    protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
        Map<String, Object> map = new HashMap<>();
        map.put("success", false);
        map.put("code", 20005);
        map.put("message", "Gateway failed");
        map.put("data", null);
        return map;
    }

    /**
     * Specifies that the response processing method is JSON processing method
     *
     * @param errorAttributes
     */
    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
    }

    /**
     * Get the corresponding HttpStatus according to the code
     *
     * @param errorAttributes
     */
    @Override
    protected int getHttpStatus(Map<String, Object> errorAttributes) {
        return 200;
    }
}

17.Redis cache

The home page data is cached through Redis. The Redis cache configuration class RedisConfig:

//Enable cache
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory
                                                               factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new
                Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setConnectionFactory(factory);
        //key serialization method
        template.setKeySerializer(redisSerializer);
        //value serialization
        template.setValueSerializer(jackson2JsonRedisSerializer);
        //value hashmap serialization
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        return template;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new
                Jackson2JsonRedisSerializer(Object.class);
        //Solve the problem of query cache conversion exception
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // Configure serialization (solve the problem of garbled code), and the expiration time is 600 seconds
        RedisCacheConfiguration config =
                RedisCacheConfiguration.defaultCacheConfig()
                        .entryTtl(Duration.ofSeconds(600))
                        .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                        .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                        .disableCachingNullValues();
        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();
        return cacheManager;
    }
}

Used to query teachers:

18. Project summary

Online education system is divided into foreground website system, background operation platform and B2C mode.

The microservice technology architecture is used, and the front and rear ends are developed separately:

  1. The main technical architecture of the backend is: springboot + springcloud + mybatis Plus + httpclient + MySQL + Maven + easyexcel + nginx
  2. The architecture of the front end is: node js + Vue. js +element-ui+NUXT+ECharts
  3. Other middleware involved include Redis, Alibaba cloud OSS and Alibaba cloud video on demand
  4. In the business, ECharts is used for chart display, EasyExcel is used to complete classification, batch addition and registration, and JWT is used for distributed single sign on

The system is divided into two parts: foreground user system and background management system:

  1. The foreground user system includes: home page, courses, famous teachers, Q & A and articles
  2. The background management system includes: Lecturer management, course classification management, course management, statistical analysis, Banner management, order management, authority management and other functions

Keywords: Java Spring Boot Back-end Interview Project

Added by polywog on Wed, 26 Jan 2022 21:53:23 +0200