项目介绍 《谷粒学院》是一个在线教育的网站
技术栈 后端
数据库
前端
搭建项目结构 创建父工程 新建一个父工程 guli_parent
修改在 guli_parent 结构下额 pom.xml 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 <?xml version="1.0" encoding="UTF-8"?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <groupId > com.szx</groupId > <artifactId > guli_parent</artifactId > <packaging > pom</packaging > <version > 0.0.1-SNAPSHOT</version > <modules > <module > service</module > <module > common</module > </modules > <name > guli_parent</name > <description > Demo project for Spring Boot</description > <properties > <java.version > 1.8</java.version > <project.build.sourceEncoding > UTF-8</project.build.sourceEncoding > <project.reporting.outputEncoding > UTF-8</project.reporting.outputEncoding > <spring-boot.version > 2.3.7.RELEASE</spring-boot.version > <guli.version > 0.0.1-SNAPSHOT</guli.version > <mybatis-plus.version > 3.0.5</mybatis-plus.version > <velocity.version > 2.0</velocity.version > <swagger.version > 2.7.0</swagger.version > <aliyun.oss.version > 2.8.3</aliyun.oss.version > <jodatime.version > 2.10.1</jodatime.version > <poi.version > 3.17</poi.version > <commons-fileupload.version > 1.3.1</commons-fileupload.version > <commons-io.version > 2.6</commons-io.version > <httpclient.version > 4.5.1</httpclient.version > <jwt.version > 0.7.0</jwt.version > <aliyun-java-sdk-core.version > 4.3.3</aliyun-java-sdk-core.version > <aliyun-sdk-oss.version > 3.1.0</aliyun-sdk-oss.version > <aliyun-java-sdk-vod.version > 2.15.2</aliyun-java-sdk-vod.version > <aliyun-java-vod-upload.version > 1.4.11</aliyun-java-vod-upload.version > <aliyun-sdk-vod-upload.version > 1.4.11</aliyun-sdk-vod-upload.version > <fastjson.version > 1.2.28</fastjson.version > <gson.version > 2.8.2</gson.version > <json.version > 20170516</json.version > <commons-dbutils.version > 1.7</commons-dbutils.version > <canal.client.version > 1.1.0</canal.client.version > <docker.image.prefix > zx</docker.image.prefix > <cloud-alibaba.version > 0.2.2.RELEASE</cloud-alibaba.version > </properties > <dependencyManagement > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-dependencies</artifactId > <version > ${spring-boot.version}</version > <type > pom</type > <scope > import</scope > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-dependencies</artifactId > <version > Hoxton.RELEASE</version > <type > pom</type > <scope > import</scope > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-alibaba-dependencies</artifactId > <version > ${cloud-alibaba.version}</version > <type > pom</type > <scope > import</scope > </dependency > <dependency > <groupId > com.baomidou</groupId > <artifactId > mybatis-plus-boot-starter</artifactId > <version > ${mybatis-plus.version}</version > </dependency > <dependency > <groupId > org.apache.velocity</groupId > <artifactId > velocity-engine-core</artifactId > <version > ${velocity.version}</version > </dependency > <dependency > <groupId > io.springfox</groupId > <artifactId > springfox-swagger2</artifactId > <version > ${swagger.version}</version > </dependency > <dependency > <groupId > io.springfox</groupId > <artifactId > springfox-swagger-ui</artifactId > <version > ${swagger.version}</version > </dependency > <dependency > <groupId > com.aliyun.oss</groupId > <artifactId > aliyun-sdk-oss</artifactId > <version > ${aliyun.oss.version}</version > </dependency > <dependency > <groupId > joda-time</groupId > <artifactId > joda-time</artifactId > <version > ${jodatime.version}</version > </dependency > <dependency > <groupId > org.apache.poi</groupId > <artifactId > poi</artifactId > <version > ${poi.version}</version > </dependency > <dependency > <groupId > org.apache.poi</groupId > <artifactId > poi-ooxml</artifactId > <version > ${poi.version}</version > </dependency > <dependency > <groupId > commons-fileupload</groupId > <artifactId > commons-fileupload</artifactId > <version > ${commons-fileupload.version}</version > </dependency > <dependency > <groupId > commons-io</groupId > <artifactId > commons-io</artifactId > <version > ${commons-io.version}</version > </dependency > <dependency > <groupId > org.apache.httpcomponents</groupId > <artifactId > httpclient</artifactId > <version > ${httpclient.version}</version > </dependency > <dependency > <groupId > com.google.code.gson</groupId > <artifactId > gson</artifactId > <version > ${gson.version}</version > </dependency > <dependency > <groupId > io.jsonwebtoken</groupId > <artifactId > jjwt</artifactId > <version > ${jwt.version}</version > </dependency > <dependency > <groupId > com.aliyun</groupId > <artifactId > aliyun-java-sdk-core</artifactId > <version > ${aliyun-java-sdk-core.version}</version > </dependency > <dependency > <groupId > com.aliyun.oss</groupId > <artifactId > aliyun-sdk-oss</artifactId > <version > ${aliyun-sdk-oss.version}</version > </dependency > <dependency > <groupId > com.aliyun</groupId > <artifactId > aliyun-java-sdk-vod</artifactId > <version > ${aliyun-java-sdk-vod.version}</version > </dependency > <dependency > <groupId > com.aliyun</groupId > <artifactId > aliyun-java-vod-upload</artifactId > <version > ${aliyun-java-vod-upload.version}</version > </dependency > <dependency > <groupId > com.aliyun</groupId > <artifactId > aliyun-sdk-vod-upload</artifactId > <version > ${aliyun-sdk-vod-upload.version}</version > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > fastjson</artifactId > <version > ${fastjson.version}</version > </dependency > <dependency > <groupId > org.json</groupId > <artifactId > json</artifactId > <version > ${json.version}</version > </dependency > <dependency > <groupId > commons-dbutils</groupId > <artifactId > commons-dbutils</artifactId > <version > ${commons-dbutils.version}</version > </dependency > <dependency > <groupId > com.alibaba.otter</groupId > <artifactId > canal.client</artifactId > <version > ${canal.client.version}</version > </dependency > <dependency > <groupId > com.szx</groupId > <artifactId > service-base</artifactId > <version > 0.0.1-SNAPSHOT</version > </dependency > </dependencies > </dependencyManagement > <build > <plugins > <plugin > <groupId > org.apache.maven.plugins</groupId > <artifactId > maven-compiler-plugin</artifactId > <version > 3.8.1</version > <configuration > <source > 1.8</source > <target > 1.8</target > <encoding > UTF-8</encoding > </configuration > </plugin > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > <version > 2.3.7.RELEASE</version > <configuration > <mainClass > com.szx.guli_parent.GuliParentApplication</mainClass > </configuration > <executions > <execution > <id > repackage</id > <goals > <goal > repackage</goal > </goals > </execution > </executions > </plugin > </plugins > </build > </project >
新建service包 在 guli_parnet 目录下新建 service Modal 包
修改 service 包下面的 pom.xml 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 <?xml version="1.0" encoding="UTF-8"?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <parent > <artifactId > guli_parent</artifactId > <groupId > com.szx</groupId > <version > 0.0.1-SNAPSHOT</version > </parent > <modelVersion > 4.0.0</modelVersion > <artifactId > service</artifactId > <packaging > pom</packaging > <modules > <module > service_edu</module > </modules > <properties > <maven.compiler.source > 8</maven.compiler.source > <maven.compiler.target > 8</maven.compiler.target > </properties > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > com.baomidou</groupId > <artifactId > mybatis-plus-boot-starter</artifactId > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > </dependency > <dependency > <groupId > org.apache.velocity</groupId > <artifactId > velocity-engine-core</artifactId > </dependency > <dependency > <groupId > io.springfox</groupId > <artifactId > springfox-swagger2</artifactId > </dependency > <dependency > <groupId > io.springfox</groupId > <artifactId > springfox-swagger-ui</artifactId > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > </dependency > <dependency > <groupId > org.apache.poi</groupId > <artifactId > poi</artifactId > </dependency > <dependency > <groupId > org.apache.poi</groupId > <artifactId > poi-ooxml</artifactId > </dependency > <dependency > <groupId > commons-fileupload</groupId > <artifactId > commons-fileupload</artifactId > </dependency > <dependency > <groupId > org.apache.httpcomponents</groupId > <artifactId > httpclient</artifactId > </dependency > <dependency > <groupId > commons-io</groupId > <artifactId > commons-io</artifactId > </dependency > <dependency > <groupId > com.google.code.gson</groupId > <artifactId > gson</artifactId > </dependency > <dependency > <groupId > junit</groupId > <artifactId > junit</artifactId > <version > 4.12</version > </dependency > <dependency > <groupId > com.szx</groupId > <artifactId > service-base</artifactId > <version > 0.0.1-SNAPSHOT</version > </dependency > <dependency > <groupId > com.szx</groupId > <artifactId > common_utils</artifactId > <version > 0.0.1-SNAPSHOT</version > </dependency > </dependencies > </project >
新建 service_edu 在上面创建的 service 包下创建子子包 service_edu
新建配置文件 在 service_edu 包下的 resource 文件夹中新建 application.properties
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 server.port =8001 spring.application.name =service_edu spring.profiles.active =dev spring.jackson.date-format =yyyy-MM-dd HH:mm:ss spring.jackson.time-zone =GMT+8 spring.datasource.driver-class-name =com.mysql.cj.jdbc.Driver spring.datasource.url =jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8 spring.datasource.username =root spring.datasource.password =abc123 mybatis-plus.configuration.log-impl =org.apache.ibatis.logging.stdout.StdOutImpl
创建启动类 在 service_edu 包中创建启动类
配置代码自动生成器 代码结构比较固定,直接复制修改输入的位置和连接的数据库,然后运行即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 package com.szx;import com.baomidou.mybatisplus.annotation.DbType;import com.baomidou.mybatisplus.annotation.IdType;import com.baomidou.mybatisplus.generator.AutoGenerator;import com.baomidou.mybatisplus.generator.config.DataSourceConfig;import com.baomidou.mybatisplus.generator.config.GlobalConfig;import com.baomidou.mybatisplus.generator.config.PackageConfig;import com.baomidou.mybatisplus.generator.config.StrategyConfig;import com.baomidou.mybatisplus.generator.config.rules.DateType;import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;import org.junit.Test;public class CodeGenerator { @Test public void run () { AutoGenerator mpg = new AutoGenerator(); GlobalConfig gc = new GlobalConfig(); String projectPath = System.getProperty("user.dir" ); gc.setOutputDir("D:\\0000学习文件\\Java项目练习\\java_product_guli\\guli_parent\\service\\service_edu" + "/src/main/java" ); gc.setAuthor("szx" ); gc.setOpen(false ); gc.setFileOverride(false ); gc.setServiceName("%sService" ); gc.setIdType(IdType.ID_WORKER); gc.setDateType(DateType.ONLY_DATE); gc.setSwagger2(true ); mpg.setGlobalConfig(gc); 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("abc123" ); dsc.setDbType(DbType.MYSQL); mpg.setDataSource(dsc); PackageConfig pc = new PackageConfig(); pc.setParent("com.szx" ); pc.setModuleName("edu" ); pc.setController("controller" ); pc.setEntity("entity" ); pc.setService("service" ); pc.setMapper("mapper" ); mpg.setPackageInfo(pc); StrategyConfig strategy = new StrategyConfig(); strategy.setInclude("edu_teacher" ); strategy.setNaming(NamingStrategy.underline_to_camel); strategy.setTablePrefix(pc.getModuleName() + "_" ); strategy.setColumnNaming(NamingStrategy.underline_to_camel); strategy.setEntityLombokModel(true ); strategy.setRestControllerStyle(true ); strategy.setControllerMappingHyphenStyle(true ); mpg.setStrategy(strategy); mpg.execute(); } }
逻辑删除插件 在 service_edu 包下新建配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.szx.edu.config;import com.baomidou.mybatisplus.core.injector.ISqlInjector;import com.baomidou.mybatisplus.extension.injector.LogicSqlInjector;import org.mybatis.spring.annotation.MapperScan;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configuration @MapperScan("com.szx.edu.mapper") public class EduConfig { @Bean public ISqlInjector sqlInjector () { return new LogicSqlInjector(); } }
在实体类上添加 @TableLogic 注解表示该字段表示是否删除的字段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 package com.szx.edu.entity;import com.baomidou.mybatisplus.annotation.*;import java.util.Date;import java.io.Serializable;import io.swagger.annotations.ApiModel;import io.swagger.annotations.ApiModelProperty;import lombok.Data;import lombok.EqualsAndHashCode;import lombok.experimental.Accessors;@Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @TableName("edu_teacher") @ApiModel(value="Teacher对象", description="讲师") public class Teacher implements Serializable { private static final long serialVersionUID = 1L ; @ApiModelProperty(value = "讲师ID") @TableId(value = "id", type = IdType.AUTO) private Long id; @ApiModelProperty(value = "讲师姓名") private String name; @ApiModelProperty(value = "讲师简介") private String intro; @ApiModelProperty(value = "讲师资历,一句话说明讲师") private String career; @ApiModelProperty(value = "头衔 1高级讲师 2首席讲师") private Integer level; @ApiModelProperty(value = "讲师头像") private String avatar; @ApiModelProperty(value = "排序") private Integer sort; @ApiModelProperty(value = "逻辑删除 1(true)已删除, 0(false)未删除") @TableLogic private Integer isDeleted; @ApiModelProperty(value = "创建时间") private Date gmtCreate; @ApiModelProperty(value = "更新时间") private Date gmtModified; }
讲师查询和删除 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 package com.szx.edu.controller;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;import com.baomidou.mybatisplus.core.toolkit.StringUtils;import com.szx.commonutils.Msg;import com.szx.edu.entity.Teacher;import com.szx.edu.service.TeacherService;import io.swagger.annotations.Api;import io.swagger.annotations.ApiOperation;import io.swagger.annotations.ApiParam;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;import java.util.List;@Api(tags = "讲师模块") @CrossOrigin @RestController @RequestMapping("/edu/teacher") public class TeacherController { @Autowired TeacherService teacherService; @ApiOperation("根据条件获取所有讲师") @GetMapping("getAllTeacher") public Msg getAllTeacher (@ApiParam(name = "name",value = "讲师姓名") @RequestParam("name") String name) { QueryWrapper<Teacher> qw = new QueryWrapper<>(); qw.like("name" ,name); List<Teacher> teacherList = teacherService.list(qw); return Msg.Ok().data("rows" ,teacherList); } @ApiOperation("根据讲师id删除讲师") @DeleteMapping("{id}") public Msg deleteTeacher (@ApiParam(name = "id",value = "讲师id") @PathVariable String id) { boolean flag = teacherService.removeById(id); if (flag){ return Msg.Ok(); }else { return Msg.Error(); } } }
引入swagger 新建common 在 guli_parent 父工程下新建一个 common 包,表示一些公共的配置
修改 pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 <?xml version="1.0" encoding="UTF-8"?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <parent > <artifactId > guli_parent</artifactId > <groupId > com.szx</groupId > <version > 0.0.1-SNAPSHOT</version > </parent > <modelVersion > 4.0.0</modelVersion > <artifactId > common</artifactId > <packaging > pom</packaging > <modules > <module > service-base</module > <module > common_utils</module > </modules > <properties > <maven.compiler.source > 8</maven.compiler.source > <maven.compiler.target > 8</maven.compiler.target > </properties > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > <scope > provided</scope > </dependency > <dependency > <groupId > com.baomidou</groupId > <artifactId > mybatis-plus-boot-starter</artifactId > <scope > provided</scope > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <scope > provided</scope > </dependency > <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 > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-redis</artifactId > </dependency > </dependencies > </project >
新建service-base 在 common 包下新建 service-base 包
新建SwaggerConfig 在如下位置新建
代码内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package com.szx.servicebase.config;import com.google.common.base.Predicates;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import springfox.documentation.builders.ApiInfoBuilder;import springfox.documentation.builders.PathSelectors;import springfox.documentation.service.ApiInfo;import springfox.documentation.service.Contact;import springfox.documentation.spi.DocumentationType;import springfox.documentation.spring.web.plugins.Docket;import springfox.documentation.swagger2.annotations.EnableSwagger2;@Configuration @EnableSwagger2 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(); } public ApiInfo webApiInfo () { return new ApiInfoBuilder() .title("网站-课程中心Api文档" ) .description("文本档描述了课程中心微服务定义的接口" ) .version("1.0" ) .contact(new Contact("Helen" , "http://atguigu.com" ,"55317332@qq.com" )) .build(); } }
引入swagger依赖 在 service 包下的 pom 文件中引入我们自己创建的 service-base 包
还要再启动类上添加 @ComponentScan(basePackages = {"com.szx"})
注解,表示全局扫描以 com.szx 文件夹下面的所有组件
访问swagger页面 启动程序,在项目地址后面添加固定的地址 /swagger-ui.html
添加启动地址信息 修改启动类 main 方法
@SneakyThrows 注解会自动帮我们的代码生成 try catch并向上抛出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package com.szx.edu;import lombok.SneakyThrows;import lombok.extern.log4j.Log4j2;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.ConfigurableApplicationContext;import org.springframework.context.annotation.ComponentScan;import org.springframework.core.env.Environment;import java.net.InetAddress;@Log4j2 @SpringBootApplication @ComponentScan(basePackages = {"com.szx"}) public class EduApplication { @SneakyThrows public static void main (String[] args) { ConfigurableApplicationContext application = SpringApplication.run(EduApplication.class, args); Environment env = application.getEnvironment(); String host = InetAddress.getLocalHost().getHostAddress(); String port = env.getProperty("server.port" ); log.info("\n ----------------------------------------------------------\n\t" + "Application '{}' 正在运行中... Access URLs:\n\t" + "Local: \t\thttp://localhost:{}\n\t" + "External: \thttp://{}:{}\n\t" + "Doc: \thttp://{}:{}/doc.html\n\t" + "SwaggerDoc: \thttp://{}:{}/swagger-ui.html\n\t" + "----------------------------------------------------------" , env.getProperty("spring.application.name" ), env.getProperty("server.port" ), host, port, host, port, host, port); } }
启动效果
统一返回对象 新建common_utils 在 common 包下新建 common_utils 包
然后定义一个状态码接口
新建Msg类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 package com.szx.commonutils;import lombok.Data;import java.util.HashMap;import java.util.Map;@Data public class Msg { Integer code; String msg; boolean success; Map<String,Object> data = new HashMap<>(); private Msg () {}; public static Msg Ok () { Msg msg = new Msg(); msg.setCode(ResultCode.SUCCESS); msg.setMsg("成功" ); msg.setSuccess(true ); return msg; } public static Msg Error () { Msg msg = new Msg(); msg.setCode(ResultCode.ERROR); msg.setMsg("失败" ); msg.setSuccess(false ); return msg; } public Msg msg (String msg) { this .setMsg(msg); return this ; } public Msg data (HashMap<String,Object> map) { this .setData(map); return this ; } public Msg data (String key,Object value) { this .data.put(key,value); return this ; } }
引用common_utils 在 service 包下面的 pom 文件中引入 common_utils
使用 1 2 3 4 5 6 7 8 9 10 11 12 13 @ApiOperation("根据条件获取所有讲师") @GetMapping("getAllTeacher") public Msg getAllTeacher (@ApiParam(name = "name",value = "讲师姓名") @RequestParam("name") String name) { QueryWrapper<Teacher> qw = new QueryWrapper<>(); qw.like("name" ,name); List<Teacher> teacherList = teacherService.list(qw); return Msg.Ok().data("rows" ,teacherList); }
返回的格式如下
分页插件使用 添加分页查询插件
在config配置类中添加分页插件
代码如下
1 2 3 4 5 6 7 8 @Bean public PaginationInterceptor paginationInterceptor () { return new PaginationInterceptor(); }
添加分页查询讲师的接口
1 2 3 4 5 6 7 8 9 10 @ApiOperation("分页查询讲师") @GetMapping("pageTeacher/{pageNum}/{pageSize}") public Msg pageTeacher (@ApiParam(value = "页码",required = true) @PathVariable("pageNum") Long pageNum, @ApiParam(value = "每页总条数",required = true) @PathVariable("pageSize") Long pageSize) { Page<Teacher> teacherPage = new Page<>(pageNum, pageSize); teacherService.page(teacherPage,null ); long total = teacherPage.getTotal(); List<Teacher> list = teacherPage.getRecords(); return Msg.Ok().data("total" ,total).data("rows" ,list); }
多条件分页查询讲师接口 首先新建一个 TeacherVo
1 2 3 4 5 6 7 8 9 10 11 @Data public class TeacherVo { @ApiModelProperty("姓名") String name; @ApiModelProperty("讲师等级,1:高级讲师,2:首席讲师") Integer level; @ApiModelProperty("开始时间") String startTime; @ApiModelProperty("结束时间") String endTime; }
编写分页带条件查询接口
这里使用 @RequestBody 注解,表示通过 JSON 的方式获取前端传递过来的对象,自动封装到 teacherVo 实例中,并且使用了 @RequestBody 注解后接口的请求方式必须是 post 请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 @ApiOperation("多条件分页查询讲师接口") @PostMapping("paramteacher/{pageNum}/{pageSize}") public Msg paramTeacher (@ApiParam(value = "页码",required = true) @PathVariable("pageNum") Long pageNum, @ApiParam(value = "每页总条数",required = true) @PathVariable("pageSize") Long pageSize, @RequestBody TeacherVo teacherVo) { Page<Teacher> teacherPage = new Page<>(pageNum, pageSize); QueryWrapper<Teacher> qw = new QueryWrapper<>(); String name = teacherVo.getName(); Integer level = teacherVo.getLevel(); String startTime = teacherVo.getStartTime(); String endTime = teacherVo.getEndTime(); if (!StringUtils.isEmpty(name)){ qw.like("name" ,name); } if (!StringUtils.isEmpty(level)){ qw.like("level" ,level); } if (!StringUtils.isEmpty(startTime)){ qw.ge("gmt_create" ,startTime); } if (!StringUtils.isEmpty(endTime)){ qw.le("gmt_create" ,endTime); } teacherService.page(teacherPage,qw); long total = teacherPage.getTotal(); List<Teacher> list = teacherPage.getRecords(); return Msg.Ok().data("total" ,total).data("rows" ,list); }
自动填充创建时间和更新时间 首先在实体类中添加对应的注解
@TableField(fill = FieldFill.INSERT)
自动填充创建时间
@TableField(fill = FieldFill.INSERT_UPDATE)
自动填充更新时间
1 2 3 4 5 6 7 @ApiModelProperty(value = "创建时间") @TableField(fill = FieldFill.INSERT) private Date gmtCreate;@ApiModelProperty(value = "更新时间") @TableField(fill = FieldFill.INSERT_UPDATE) private Date gmtModified;
然后添加 MetaObjectHandler 接口实现类
在 service-base 包下新建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package com.szx.servicebase.config;import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;import org.apache.ibatis.reflection.MetaObject;import org.springframework.stereotype.Component;import java.util.Date;@Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill (MetaObject metaObject) { this .setFieldValByName("gmtCreate" , new Date(), metaObject); this .setFieldValByName("gmtModified" , new Date(), metaObject); } @Override public void updateFill (MetaObject metaObject) { this .setFieldValByName("gmtModified" , new Date(), metaObject); } }
讲师添加方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @ApiOperation("添加讲师") @PostMapping("addteacher") public Msg addTeacher (@RequestBody Teacher teacher) { boolean save = teacherService.save(teacher); if (save){ return Msg.Ok(); }else { return Msg.Error(); } }
新增时不需要传递id、创建时间和修改时间
修改讲师方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @ApiOperation("根据讲师id查询单条讲师数据") @GetMapping("getTeacherById/{id}") public Msg getTeacherById (@PathVariable("id") Long id) { Teacher teacher = teacherService.getById(id); return Msg.Ok().data("info" ,teacher); } @ApiOperation("根据讲师id修改讲师") @PostMapping("updateTeacher") public Msg updateTeacher (@RequestBody Teacher teacher) { boolean b = teacherService.updateById(teacher); return b ? Msg.Ok() : Msg.Error(); }
添加统一错误处理 首先在 service-base 包中添加处理类:GlobalExceptionHandler
1 2 3 4 5 6 7 8 9 10 11 @ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) @ResponseBody public Msg error (Exception e) { e.printStackTrace(); return Msg.Error().msg(e.toString()); } }
这里默认找不到 Msg 类,是因为我们需要在 service-base 包下的 pom 文件中引入 common_utils 包
然后再 service_edu 中就不要重复的使用 common_utils 依赖
测试错误返回 我们在根据id查询讲师的方法中手动添加一个异常代码
然后调用这个接口,可以看出返回的是经过我们统一处理后的结果
特定异常处理 针对某个特定的异常做处理,例如,针对空指针异常
还是在 GlobalExceptionHandler 类中添加异常处理方法
1 2 3 4 5 6 7 8 @ExceptionHandler(NullPointerException.class) @ResponseBody public Msg error (NullPointerException e) { e.printStackTrace(); return Msg.Error().msg(e.toString()); }
自定义异常处理 首先新建一个自定义异常类并且继承 RuntimeException
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.szx.servicebase.exceptionhandler;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;@Data @AllArgsConstructor @NoArgsConstructor public class GuliException extends RuntimeException { public Integer code; public String msg; }
然后还是在 GlobalExceptionHandler 中添加自定义的错误处理
1 2 3 4 5 6 7 8 @ExceptionHandler(GuliException.class) @ResponseBody public Msg error (GuliException e) { e.printStackTrace(); return Msg.Error().msg(e.getMsg()); }
测试自定义异常 发生异常时,需要手动的抛出自定义的异常
1 2 3 4 5 6 7 try { Map<String,Object> map = null ; map.put("name" ,"张三" ); }catch (Exception e){ throw new GuliException(500 ,"自定义GuliException异常" ); }
返回结果
统一日志处理 添加日志配置文件 首先要删除 application.properties 中有关日志的配置
在 resources 文件夹中添加 logback-spring.xml 配置文件,名字是固定的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 <?xml version="1.0" encoding="UTF-8"?> <configuration scan ="true" scanPeriod ="10 seconds" > <contextName > logback</contextName > <property name ="log.path" value ="D:/guli_1010/edu" /> <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)" /> <appender name ="CONSOLE" class ="ch.qos.logback.core.ConsoleAppender" > <filter class ="ch.qos.logback.classic.filter.ThresholdFilter" > <level > INFO</level > </filter > <encoder > <Pattern > ${CONSOLE_LOG_PATTERN}</Pattern > <charset > UTF-8</charset > </encoder > </appender > <appender name ="INFO_FILE" class ="ch.qos.logback.core.rolling.RollingFileAppender" > <file > ${log.path}/log_info.log</file > <encoder > <pattern > %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern > <charset > UTF-8</charset > </encoder > <rollingPolicy class ="ch.qos.logback.core.rolling.TimeBasedRollingPolicy" > <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 > <maxHistory > 15</maxHistory > </rollingPolicy > <filter class ="ch.qos.logback.classic.filter.LevelFilter" > <level > INFO</level > <onMatch > ACCEPT</onMatch > <onMismatch > DENY</onMismatch > </filter > </appender > <appender name ="WARN_FILE" class ="ch.qos.logback.core.rolling.RollingFileAppender" > <file > ${log.path}/log_warn.log</file > <encoder > <pattern > %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern > <charset > UTF-8</charset > </encoder > <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 > <maxHistory > 15</maxHistory > </rollingPolicy > <filter class ="ch.qos.logback.classic.filter.LevelFilter" > <level > warn</level > <onMatch > ACCEPT</onMatch > <onMismatch > DENY</onMismatch > </filter > </appender > <appender name ="ERROR_FILE" class ="ch.qos.logback.core.rolling.RollingFileAppender" > <file > ${log.path}/log_error.log</file > <encoder > <pattern > %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern > <charset > UTF-8</charset > </encoder > <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 > <maxHistory > 15</maxHistory > </rollingPolicy > <filter class ="ch.qos.logback.classic.filter.LevelFilter" > <level > ERROR</level > <onMatch > ACCEPT</onMatch > <onMismatch > DENY</onMismatch > </filter > </appender > <springProfile name ="dev" > <logger name ="com.szx" level ="INFO" /> <root level ="INFO" > <appender-ref ref ="CONSOLE" /> <appender-ref ref ="INFO_FILE" /> <appender-ref ref ="WARN_FILE" /> <appender-ref ref ="ERROR_FILE" /> </root > </springProfile > <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 >
输出日志到文件 在 GlobalExceptionHandler 类上添加 @Slf4j 注解
将日志信息写入到文件
log.error(e.getMessage());
1 2 3 4 5 6 7 8 9 10 @ExceptionHandler(GuliException.class) @ResponseBody public Msg error (GuliException e) { log.error(e.getMessage()); e.printStackTrace(); return Msg.Error().msg(e.getMsg()); }
测试日志打印 启动项目,可以看到控制台的打印颜色会发生变化
然后触发一个异常
打开 D:\guli_1010\edu
文件夹查看,发现已经有日志文件自动的保存在电脑中
查看 error.log 日志内容,当前错误的日志内容比较简单,下面可以将全部的错误信息保存在日志文件中
将日志堆栈信息输出到日志 新建 ExceptionUtils 类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package com.szx.servicebase.utils;import java.io.IOException;import java.io.PrintWriter;import java.io.StringWriter;public class ExceptionUtil { public static String getMessage (Exception e) { StringWriter sw = null ; PrintWriter pw = null ; try { sw = new StringWriter(); pw = new PrintWriter(sw); e.printStackTrace(pw); pw.flush(); sw.flush(); } finally { if (sw != null ) { try { sw.close(); } catch (IOException e1) { e1.printStackTrace(); } } if (pw != null ) { pw.close(); } } return sw.toString(); } }
重写 GuliException 的 toString 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Data @AllArgsConstructor @NoArgsConstructor public class GuliException extends RuntimeException { public Integer code; public String msg; @Override public String toString () { return "GuliException{" + "message=" + this .getMessage() + ", code=" + code + '}' ; } }
输入日志到文件
1 log.error(ExceptionUtil.getMessage(e));
查看日志内容
添加前端后台管理系统模板 官网地址:
https://panjiachen.gitee.io/vue-element-admin-site/zh/
上传文件到阿里云OSS 获取AccessKey 首先登录 OSS 控制台查看自己的 AccessKey
然后创建bucket
添加service_oss模块 在 service 下面新建子包,并初始化如下配置文件
配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 server.port=8002 spring.application.name=service_oss spring.profiles.active=dev aliyun.oss.file.endpoint=oss-cn-hangzhou.aliyuncs.com aliyun.oss.file.keyid=your oss key aliyun.oss.file.keysecret=xxxxxxxx aliyun.oss.file.bucketname=szx-bucket1 aliyun.oss.file.bucketurl=https://szx-bucket1.oss-cn-hangzhou.aliyuncs.com/
导入相关依赖
1 2 3 4 5 6 7 8 9 10 11 <dependency > <groupId > joda-time</groupId > <artifactId > joda-time</artifactId > </dependency > <dependency > <groupId > com.aliyun.oss</groupId > <artifactId > aliyun-sdk-oss</artifactId > <version > 3.15.0</version > </dependency >
创建启动类,并且在启动类上添加排除数据源的配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package com.szx.oss;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;import org.springframework.context.annotation.ComponentScan;@SpringBootApplication(exclude = DataSourceAutoConfiguration.class) @ComponentScan(basePackages = {"com.szx"}) public class OssApplication { public static void main (String[] args) { SpringApplication.run(OssApplication.class,args); } }
添加属性工具类 实现 InitializingBean 接口并重写 afterPropertiesSet 方法,会在这个组件初始化完成后调用 afterPropertiesSet 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package com.szx.oss.utils;import org.springframework.beans.factory.InitializingBean;import org.springframework.beans.factory.annotation.Value;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.context.annotation.PropertySource;import org.springframework.stereotype.Component;@Component public class OssPropertyUtils implements InitializingBean { @Value("${aliyun.oss.file.endpoint}") private String endpoint; @Value("${aliyun.oss.file.keyid}") private String keyid; @Value("${aliyun.oss.file.keysecret}") private String keysecret; @Value("${aliyun.oss.file.bucketname}") private String bucketname; @Value("${aliyun.oss.file.bucketurl}") private String bucketurl; public static String ENDPOINT; public static String KEYID; public static String KEYSECRET; public static String BUCKETNAME; public static String BUCKETURL; @Override public void afterPropertiesSet () throws Exception { OssPropertyUtils.ENDPOINT=endpoint; OssPropertyUtils.KEYID=keyid; OssPropertyUtils.KEYSECRET=keysecret; OssPropertyUtils.BUCKETNAME=bucketname; OssPropertyUtils.BUCKETURL=bucketurl; } }
添加OssService接口和实现类 OssService接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.szx.oss.service;import org.springframework.web.multipart.MultipartFile;public interface OssService { String upload (MultipartFile file) ; }
OssService实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 package com.szx.oss.service.impl;import com.aliyun.oss.OSS;import com.aliyun.oss.OSSClientBuilder;import com.aliyun.oss.OSSException;import com.aliyun.oss.model.PutObjectRequest;import com.szx.oss.service.OssService;import com.szx.oss.utils.OssPropertyUtils;import org.springframework.stereotype.Service;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.multipart.MultipartFile;import java.io.ByteArrayInputStream;import java.io.IOException;import java.io.InputStream;@Service public class OssServiceImpl implements OssService { @Override public String upload (MultipartFile file) { String endpoint = OssPropertyUtils.ENDPOINT; String accessKeyId = OssPropertyUtils.KEYID; String accessKeySecret = OssPropertyUtils.KEYSECRET; String bucketName = OssPropertyUtils.BUCKETNAME; String objectName = "guli/" + file.getResource().getFilename(); try { OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); InputStream inputStream = file.getInputStream(); ossClient.putObject(bucketName, objectName, inputStream); ossClient.shutdown(); } catch (IOException e) { e.printStackTrace(); } return OssPropertyUtils.BUCKETURL + objectName; } }
添加controller完成接口调用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package com.szx.oss.controller;import com.szx.commonutils.Msg;import com.szx.oss.service.OssService;import com.szx.oss.service.impl.OssServiceImpl;import io.swagger.annotations.Api;import io.swagger.annotations.ApiOperation;import io.swagger.annotations.Tag;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.multipart.MultipartFile;@Api(tags = "OSS文件上传") @RestController @RequestMapping("/server/oss") public class OssController { @Autowired OssServiceImpl ossService; @ApiOperation("文件上传") @PostMapping("upload") public Msg upload (@RequestBody MultipartFile file) { String url = ossService.upload(file); return Msg.Ok().data("url" ,url); } }
测试 通过 swagger 来测试文件上传
查看阿里云oss控制台
文件上传完善 上传相同的文件时,后面的会覆盖前面以上传的图片,我们可以通过 uuid 加 时间的方式来防止文件名重复
1 2 3 4 5 6 7 8 String objectName = file.getResource().getFilename(); String uuid = UUID.randomUUID().toString().replaceAll("-" , "" ); objectName = uuid + objectName; String datePath = new DateTime().toString("yyyy/MM/dd" ); objectName = "guli/" + datePath + "/" + objectName;
安装Window版nginx 下载 下载地址:https://nginx.org/download/nginx-1.23.1.zip
下载后解压到没有中文的目录
启动 在 nginx.exe 目录下输入如下命令启动
快捷启动
1 2 3 d: cd D:\Install\rundir\nginx-1.23.1 nginx.exe
停止 在 nginx.exe 目录下输入如下命令停止服务
1 2 3 d: cd D:\Install\rundir\nginx-1.23.1 nginx.exe -s stop
重启
修改默认的80端口
配置服务转发 通过添加配置,匹配不同的接口路径实现接口转发的目的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 http { server { listen 9001; server_name localhost; location ~ /eduserver/ { proxy_pass http://localhost:8001; } location ~ /eduoss/ { proxy_pass http://localhost:8002; } } }
使用easyExcel 写文件 添加依赖
1 2 3 4 5 6 7 8 9 10 11 12 <dependency > <groupId > com.alibaba</groupId > <artifactId > easyexcel</artifactId > <version > 2.1.1</version > </dependency > <dependency > <groupId > org.apache.poi</groupId > <artifactId > poi</artifactId > <version > 3.17</version > </dependency >
添加一个excel对应的实体类
@ExcelProperty("学生编号")
表示要设置文件的标头名称
1 2 3 4 5 6 7 8 9 10 11 12 package com.szx;import com.alibaba.excel.annotation.ExcelProperty;import lombok.Data;@Data public class ExcelPojoDemo { @ExcelProperty("学生编号") Integer son; @ExcelProperty("学生姓名") String sname; }
添加一个测试方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package com.szx;import com.alibaba.excel.EasyExcel;import java.util.ArrayList;import java.util.List;public class ExcelWriteDemo { public static void main (String[] args) { String fileName = "D:\\学生列表.xls" ; EasyExcel.write(fileName, ExcelPojoDemo.class).sheet("学生列表" ).doWrite(getSname()); } public static List<ExcelPojoDemo> getSname () { ArrayList<ExcelPojoDemo> nameList = new ArrayList<>(); for (int i = 0 ; i < 10 ; i++) { ExcelPojoDemo pojoDemo = new ExcelPojoDemo(); pojoDemo.setSon(i); pojoDemo.setSname("jack" + i); nameList.add(pojoDemo); } return nameList; } }
运行查看 D 盘下生成的文件
读取文件 首先在实体类中添加 index 标记
1 2 3 4 5 6 7 @Data public class ExcelPojoDemo { @ExcelProperty(value = "学生编号",index = 0) Integer son; @ExcelProperty(value = "学生姓名",index = 1) String sname; }
添加文件监听器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package com.szx;import com.alibaba.excel.context.AnalysisContext;import com.alibaba.excel.event.AnalysisEventListener;import java.util.Map;public class ExcelRadeListener extends AnalysisEventListener <ExcelPojoDemo > { @Override public void invokeHeadMap (Map<Integer, String> headMap, AnalysisContext context) { System.out.println("文件表头" + headMap); } @Override public void invoke (ExcelPojoDemo excelPojoDemo, AnalysisContext analysisContext) { System.out.println(excelPojoDemo); } @Override public void doAfterAllAnalysed (AnalysisContext analysisContext) { System.out.println("--------------文件读取完成---------------" ); } }
调用读取方法
1 2 3 4 5 6 7 public static void main (String[] args) { String fileName = "D:\\学生列表.xls" ; EasyExcel.read(fileName,ExcelPojoDemo.class,new ExcelRadeListener()).sheet().doRead(); }
实现读取Excel添加课程分类 读取Excel中的内容保存至数据库 生成代码 首先根据表名自动生成代码,对应的数据库为:edu_subject
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 package com.szx;import com.baomidou.mybatisplus.annotation.DbType;import com.baomidou.mybatisplus.annotation.IdType;import com.baomidou.mybatisplus.generator.AutoGenerator;import com.baomidou.mybatisplus.generator.config.DataSourceConfig;import com.baomidou.mybatisplus.generator.config.GlobalConfig;import com.baomidou.mybatisplus.generator.config.PackageConfig;import com.baomidou.mybatisplus.generator.config.StrategyConfig;import com.baomidou.mybatisplus.generator.config.rules.DateType;import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;import org.junit.Test;public class CodeGenerator { @Test public void run () { AutoGenerator mpg = new AutoGenerator(); GlobalConfig gc = new GlobalConfig(); String projectPath = System.getProperty("user.dir" ); gc.setOutputDir("D:\\0000学习文件\\Java项目练习\\java_product_guli\\guli_parent\\service\\service_edu" + "/src/main/java" ); gc.setAuthor("szx" ); gc.setOpen(false ); gc.setFileOverride(false ); gc.setServiceName("%sService" ); gc.setIdType(IdType.ID_WORKER); gc.setDateType(DateType.ONLY_DATE); gc.setSwagger2(true ); mpg.setGlobalConfig(gc); 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("abc123" ); dsc.setDbType(DbType.MYSQL); mpg.setDataSource(dsc); PackageConfig pc = new PackageConfig(); pc.setParent("com.szx" ); pc.setModuleName("edu" ); pc.setController("controller" ); pc.setEntity("entity" ); pc.setService("service" ); pc.setMapper("mapper" ); mpg.setPackageInfo(pc); StrategyConfig strategy = new StrategyConfig(); strategy.setInclude("edu_subject" ); strategy.setNaming(NamingStrategy.underline_to_camel); strategy.setTablePrefix(pc.getModuleName() + "_" ); strategy.setColumnNaming(NamingStrategy.underline_to_camel); strategy.setEntityLombokModel(true ); strategy.setRestControllerStyle(true ); strategy.setControllerMappingHyphenStyle(true ); mpg.setStrategy(strategy); mpg.execute(); } }
添加Excel对应的实体类 添加Excel文件实体类:SubjectData
1 2 3 4 5 6 7 8 9 10 @Data public class SubjectData { @ExcelProperty(value = "一级分类",index = 0) String oneProperties; @ExcelProperty(value = "二级分类",index = 1) String twoProperties; }
接口添加方法 在 SubjectService 接口中添加方法
1 2 3 4 5 6 7 8 9 10 11 12 public interface SubjectService extends IService <Subject > { void addSubjectByExcel (MultipartFile file, SubjectService subjectService) ; }
在 SubjectService 接口实现类中实现这个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Service public class SubjectServiceImpl extends ServiceImpl <SubjectMapper , Subject > implements SubjectService { @Autowired SubjectService subjectService; @Override @SneakyThrows public void addSubjectByExcel (MultipartFile file, SubjectService subjectService) { InputStream in = file.getInputStream(); EasyExcel.read(in,SubjectData.class,new SubjectListener(subjectService)).sheet().doRead(); } }
添加监听器 添加对应的 SubjectListener
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 package com.szx.edu.listener;import com.alibaba.excel.context.AnalysisContext;import com.alibaba.excel.event.AnalysisEventListener;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;import com.szx.edu.entity.Subject;import com.szx.edu.entity.vo.SubjectData;import com.szx.edu.service.SubjectService;import lombok.SneakyThrows;public class SubjectListener extends AnalysisEventListener <SubjectData > { SubjectService subjectService; public SubjectListener () {}; public SubjectListener (SubjectService subjectService) { this .subjectService = subjectService; }; @Override public void invoke (SubjectData subjectData, AnalysisContext analysisContext) { String oneTitle = subjectData.getOneProperties(); Subject oneSubject = getOneSubject(oneTitle,"0" ); if (oneSubject == null ){ oneSubject = new Subject(); oneSubject.setTitle(oneTitle); subjectService.save(oneSubject); } String twoTitle = subjectData.getTwoProperties(); String parentId = oneSubject.getId(); Subject twoSubject = getOneSubject(twoTitle, parentId); if (twoSubject == null ){ twoSubject = new Subject(); twoSubject.setTitle(twoTitle); twoSubject.setParentId(parentId); subjectService.save(twoSubject); } } @Override public void doAfterAllAnalysed (AnalysisContext analysisContext) { } private Subject getOneSubject (String title,String parentId) { QueryWrapper<Subject> qw = new QueryWrapper<>(); qw.eq("title" ,title); qw.eq("parent_id" ,parentId); return subjectService.getOne(qw); } }
添加controller 添加controller进行调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package com.szx.edu.controller;import com.szx.commonutils.Msg;import com.szx.edu.entity.Subject;import com.szx.edu.service.SubjectService;import io.swagger.annotations.Api;import io.swagger.annotations.ApiOperation;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;import org.springframework.web.multipart.MultipartFile;import java.util.ArrayList;@Api(tags = "课程分类") @CrossOrigin @RestController @RequestMapping("/eduserver/subject") public class SubjectController { @Autowired SubjectService subjectService; @ApiOperation("添加课程分类") @PostMapping("addSubject") public Msg addSubject (MultipartFile file) { subjectService.addSubjectByExcel(file,subjectService); return Msg.Ok(); } }
swagger测试 首先准备如下内容的一个Excel文件
然后清空数据库表中的旧数据
打开swagger进行上传导入测试
查看数据库
导入成功
树形结构的格式返回分类信息 添加children属性 首先在表对应的实体类中添加一个属性children,用来表示二级分类的数据
@TableField(exist = false)
注解表示这个属性在数据库中没有,但是是必须的
1 2 3 @ApiModelProperty(value = "二级分类数据") @TableField(exist = false) private ArrayList<Subject> children;
接口添加查询方法 在 SubjectService 接口中添加 getSubjectTree 方法,返回的是一个 list
1 2 3 4 public interface SubjectService extends IService <Subject > { ArrayList<Subject> getSubjectTree () ; }
实现类中实现改方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 package com.szx.edu.service.impl;import com.alibaba.excel.EasyExcel;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;import com.szx.edu.entity.Subject;import com.szx.edu.entity.vo.SubjectData;import com.szx.edu.listener.SubjectListener;import com.szx.edu.mapper.SubjectMapper;import com.szx.edu.service.SubjectService;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import lombok.SneakyThrows;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.web.multipart.MultipartFile;import java.io.InputStream;import java.util.ArrayList;import java.util.List;@Service public class SubjectServiceImpl extends ServiceImpl <SubjectMapper , Subject > implements SubjectService { @Autowired SubjectService subjectService; @Override @SneakyThrows public void addSubjectByExcel (MultipartFile file, SubjectService subjectService) { InputStream in = file.getInputStream(); EasyExcel.read(in,SubjectData.class,new SubjectListener(subjectService)).sheet().doRead(); } @Override public ArrayList<Subject> getSubjectTree () { QueryWrapper<Subject> qw = new QueryWrapper<>(); qw.eq("parent_id" ,"0" ); ArrayList<Subject> oneList = (ArrayList<Subject>) subjectService.list(qw); oneList.forEach(item->{ QueryWrapper<Subject> qw1 = new QueryWrapper<>(); qw1.eq("parent_id" ,item.getId()); ArrayList<Subject> twoList = (ArrayList<Subject>) subjectService.list(qw1); item.setChildren(twoList); }); return oneList; } }
添加controller 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package com.szx.edu.controller;import com.szx.commonutils.Msg;import com.szx.edu.entity.Subject;import com.szx.edu.service.SubjectService;import io.swagger.annotations.Api;import io.swagger.annotations.ApiOperation;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;import org.springframework.web.multipart.MultipartFile;import java.util.ArrayList;@Api(tags = "课程分类") @CrossOrigin @RestController @RequestMapping("/eduserver/subject") public class SubjectController { @Autowired SubjectService subjectService; @ApiOperation("以树形结构的方式查询课程分类") @GetMapping("getSubject") public Msg getSubject () { ArrayList<Subject> subjectArrayList = subjectService.getSubjectTree(); return Msg.Ok().data("rows" ,subjectArrayList); } }
swagger测试
发布课程功能 根据表名生成对应代码
edu_chapter 课程章节表
edu_comment 课程评论表
edu_course 课程信息表
edu_course_description 课程简介表
edu_video 课程视频地址表
Id自动生成的策略修改 修改课程简介的id策略为 IDType=INPUT,表示手动输入
新建一个CourseVo 我们在保存课程信息时会同时将课程简介信息保存过来,但是课程简介和课程信息不在一个表,所以,我们要封装一个CourseVo,里面用来映射前端页面传递过来的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 package com.szx.edu.entity.vo;import com.baomidou.mybatisplus.annotation.*;import io.swagger.annotations.ApiModelProperty;import lombok.Data;import java.math.BigDecimal;import java.util.Date;@Data public class CourseVo { private static final long serialVersionUID = 1L ; @ApiModelProperty(value = "课程ID") private String id; @ApiModelProperty(value = "课程讲师ID") private String teacherId; @ApiModelProperty(value = "课程专业ID") private String subjectId; @ApiModelProperty(value = "课程专业父级ID") private String subjectParentId; @ApiModelProperty(value = "课程标题") private String title; @ApiModelProperty(value = "课程销售价格,设置为0则可免费观看") private BigDecimal price; @ApiModelProperty(value = "总课时") private Integer lessonNum; @ApiModelProperty(value = "课程封面图片路径") private String cover; @ApiModelProperty(value = "课程状态 Draft未发布 Normal已发布") private String status; @ApiModelProperty(value = "课程简介") private String description; }
接口类中添加保存课程的方法 CourseService 接口中增加下面的方法
1 2 3 4 public interface CourseService extends IService <Course > { String saveCourse (CourseVo courseVo) ; }
CourseServiceImpl 实现类中实现这个方法
下面用到的一个复制属性值方法
1 2 BeanUtils.copyProperties(courseVo ,course);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 package com.szx.edu.service.impl;import com.szx.edu.entity.Course;import com.szx.edu.entity.CourseDescription;import com.szx.edu.entity.vo.CourseVo;import com.szx.edu.mapper.CourseMapper;import com.szx.edu.service.CourseService;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.szx.servicebase.exceptionhandler.GuliException;import org.springframework.beans.BeanUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Service public class CourseServiceImpl extends ServiceImpl <CourseMapper , Course > implements CourseService { @Autowired CourseDescriptionServiceImpl courseDescriptionService; public String saveCourse (CourseVo courseVo) { Course course = new Course(); BeanUtils.copyProperties(courseVo ,course); boolean save = this .saveOrUpdate(course); if (!save){ throw new GuliException(500 ,"添加课程基本信息失败" ); } CourseDescription courseDescription = new CourseDescription(); courseDescription.setId(course.getId()); courseDescription.setDescription(courseVo.getDescription()); boolean save1 = courseDescriptionService.saveOrUpdate(courseDescription); if (!save1){ throw new GuliException(500 ,"添加课程简介信息失败" ); } return course.getId(); } }
添加Controller调用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package com.szx.edu.controller;import com.szx.commonutils.Msg;import com.szx.edu.entity.vo.CourseVo;import com.szx.edu.service.impl.CourseServiceImpl;import io.swagger.annotations.Api;import io.swagger.annotations.ApiOperation;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;@Api(tags = "课程模块") @CrossOrigin @RestController @RequestMapping("/eduserver/course") public class CourseController { @Autowired CourseServiceImpl courseService; @ApiOperation("添加课程基本信息") @PostMapping("saveCourse") public Msg saveCourse (@RequestBody CourseVo courseVo) { String courseId = courseService.saveCourse(courseVo); return Msg.Ok().data("id" ,courseId); } }
swagger测试 发送数据
1 2 3 4 5 6 7 8 9 10 11 12 { "cover" : "string" , "description" : "0001string" , "id" : "" , "lessonNum" : 0 , "price" : 0 , "status" : "string" , "subjectId" : "string" , "subjectParentId" : "string" , "teacherId" : "string" , "title" : "0001string" }
执行结果
查看数据库
Vue3使用tinymce富文本编辑器 简介 tinymce 富文本编辑器功能强大,效果如图
下载依赖 首先安装 tinymce,这里安装指定版本,高版本会有兼容性问题
1 npm install tinymce@5.10.2
下载主题和汉化包 在 public 文件夹新建 resource 文件夹,在 resource 文件夹下再新建 langs 文件夹和 skins 文件夹
结构如图
打开这个连接 https://gitee.com/shuiche/tinymce-vue3/blob/master/langs/zh_CN.js 下载出来 zh_CN.js 文件,放在 langs 文件夹下
然后再 node_modules 中找到 tinymce 文件夹,复制 skins 文件夹下的内容到上面新建的 skins 中
新建Utils文件 在 src/utils 文件夹新建两个文件
src/utils/tinymce.ts
src/utils/onMountedOrActivated.ts
内容分别如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const toString = Object .prototype.toStringexport function is (val?: any , type ?: any ) { return toString.call(val) === `[object ${type } ]` } export function isNumber (val?: any ) { return is(val, 'Number' ) } export function buildShortUUID ( ) { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx' .replace(/[xy]/g , function (c ) { var r = Math .random() * 16 | 0 , v = c == 'x' ? r : (r & 0x3 | 0x8 ); return v.toString(16 ); }); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import {nextTick, onMounted, onActivated} from 'vue' export function onMountedOrActivated (hook?: any ) { let mounted: any onMounted(() => { hook() nextTick(() => { mounted = true }) }) onActivated(() => { if (mounted) { hook() } }) }
封装组件 在 src/components 文件夹下新建一个 tinymce 文件夹,里面新建三个文件:helper.js、tinymce.js、Tinymce.vue,文件内容分别如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 const validEvents = [ 'onActivate' , 'onAddUndo' , 'onBeforeAddUndo' , 'onBeforeExecCommand' , 'onBeforeGetContent' , 'onBeforeRenderUI' , 'onBeforeSetContent' , 'onBeforePaste' , 'onBlur' , 'onChange' , 'onClearUndos' , 'onClick' , 'onContextMenu' , 'onCopy' , 'onCut' , 'onDblclick' , 'onDeactivate' , 'onDirty' , 'onDrag' , 'onDragDrop' , 'onDragEnd' , 'onDragGesture' , 'onDragOver' , 'onDrop' , 'onExecCommand' , 'onFocus' , 'onFocusIn' , 'onFocusOut' , 'onGetContent' , 'onHide' , 'onInit' , 'onKeyDown' , 'onKeyPress' , 'onKeyUp' , 'onLoadContent' , 'onMouseDown' , 'onMouseEnter' , 'onMouseLeave' , 'onMouseMove' , 'onMouseOut' , 'onMouseOver' , 'onMouseUp' , 'onNodeChange' , 'onObjectResizeStart' , 'onObjectResized' , 'onObjectSelected' , 'onPaste' , 'onPostProcess' , 'onPostRender' , 'onPreProcess' , 'onProgressState' , 'onRedo' , 'onRemove' , 'onReset' , 'onSaveContent' , 'onSelectionChange' , 'onSetAttrib' , 'onSetContent' , 'onShow' , 'onSubmit' , 'onUndo' , 'onVisualAid' ] const isValidKey = (key ) => validEvents.indexOf(key) !== -1 export const bindHandlers = (initEvent, listeners, editor ) => { Object .keys(listeners) .filter(isValidKey) .forEach((key ) => { const handler = listeners[key] if (typeof handler === 'function' ) { if (key === 'onInit' ) { handler(initEvent, editor) } else { editor.on(key.substring(2 ), (e ) => handler(e, editor)) } } }) }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 export const plugins = [ 'advlist anchor autolink autosave code codesample directionality fullscreen hr insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus template textpattern visualblocks visualchars wordcount' ] export const toolbar = [ 'fontsizeselect lineheight searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote undo redo removeformat subscript superscript code codesample' , 'hr bullist numlist link preview anchor pagebreak insertdatetime media forecolor backcolor fullscreen' ]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 <template> <div class="prefixCls" :style="{ width: containerWidth }"> <textarea :id="tinymceId" ref="elRef" :style="{ visibility: 'hidden' }" ></textarea> </div> </template> <script setup> import tinymce from 'tinymce/tinymce' import 'tinymce/themes/silver' import 'tinymce/icons/default/icons' import 'tinymce/plugins/advlist' import 'tinymce/plugins/anchor' import 'tinymce/plugins/autolink' import 'tinymce/plugins/autosave' import 'tinymce/plugins/code' import 'tinymce/plugins/codesample' import 'tinymce/plugins/directionality' import 'tinymce/plugins/fullscreen' import 'tinymce/plugins/hr' import 'tinymce/plugins/insertdatetime' import 'tinymce/plugins/link' import 'tinymce/plugins/lists' import 'tinymce/plugins/media' import 'tinymce/plugins/nonbreaking' import 'tinymce/plugins/noneditable' import 'tinymce/plugins/pagebreak' import 'tinymce/plugins/paste' import 'tinymce/plugins/preview' import 'tinymce/plugins/print' import 'tinymce/plugins/save' import 'tinymce/plugins/searchreplace' import 'tinymce/plugins/spellchecker' import 'tinymce/plugins/tabfocus' import 'tinymce/plugins/template' import 'tinymce/plugins/textpattern' import 'tinymce/plugins/visualblocks' import 'tinymce/plugins/visualchars' import 'tinymce/plugins/wordcount' // import 'tinymce/plugins/table'; import { computed, nextTick, ref, unref, watch, onDeactivated, onBeforeUnmount, defineProps, defineEmits, getCurrentInstance } from 'vue' import {toolbar, plugins} from './tinymce' import {buildShortUUID, isNumber} from '/@/utils/tinymce.ts' import {bindHandlers} from './helper' import {onMountedOrActivated} from '/@/utils/onMountedOrActivated' const props = defineProps({ options: { type: Object, default: () => { } }, value: { type: String }, toolbar: { type: Array, default: toolbar }, plugins: { type: Array, default: plugins }, modelValue: { type: String }, height: { type: [Number, String], required: false, default: 400 }, width: { type: [Number, String], required: false, default: 'auto' }, showImageUpload: { type: Boolean, default: true } }) const emits = defineEmits(['change', 'update:modelValue', 'inited', 'init-error']) const {attrs} = getCurrentInstance() const tinymceId = ref(buildShortUUID('tiny-vue')) const containerWidth = computed(() => { const width = props.width if (isNumber(width)) { return `${width}px` } return width }) const editorRef = ref(null) const fullscreen = ref(false) const elRef = ref(null) const tinymceContent = computed(() => props.modelValue) const initOptions = computed(() => { const {height, options, toolbar, plugins} = props const publicPath = '/' return { selector: `#${unref(tinymceId)}`, height, toolbar, menubar: 'file edit insert view format table', plugins, language_url: '/resource/tinymce/langs/zh_CN.js', language: 'zh_CN', branding: false, default_link_target: '_blank', link_title: false, object_resizing: false, auto_focus: true, skin: 'oxide', skin_url: '/resource/tinymce/skins/ui/oxide', content_css: '/resource/tinymce/skins/ui/oxide/content.min.css', ...options, setup: (editor) => { editorRef.value = editor editor.on('init', (e) => initSetup(e)) } } }) const disabled = computed(() => { const {options} = props const getdDisabled = options && Reflect.get(options, 'readonly') const editor = unref(editorRef) if (editor) { editor.setMode(getdDisabled ? 'readonly' : 'design') } return getdDisabled ?? false }) watch( () => attrs.disabled, () => { const editor = unref(editorRef) if (!editor) { return } editor.setMode(attrs.disabled ? 'readonly' : 'design') } ) onMountedOrActivated(() => { if (!initOptions.value.inline) { tinymceId.value = buildShortUUID('tiny-vue') } nextTick(() => { setTimeout(() => { initEditor() }, 30) }) }) onBeforeUnmount(() => { destory() }) onDeactivated(() => { destory() }) function destory() { if (tinymce !== null) { // tinymce?.remove?.(unref(initOptions).selector!); } } function initSetup(e) { const editor = unref(editorRef) if (!editor) { return } const value = props.modelValue || '' editor.setContent(value) bindModelHandlers(editor) bindHandlers(e, attrs, unref(editorRef)) } function initEditor() { const el = unref(elRef) if (el) { el.style.visibility = '' } tinymce .init(unref(initOptions)) .then((editor) => { emits('inited', editor) }) .catch((err) => { emits('init-error', err) }) } function setValue(editor, val, prevVal) { if ( editor && typeof val === 'string' && val !== prevVal && val !== editor.getContent({format: attrs.outputFormat}) ) { editor.setContent(val) } } function bindModelHandlers(editor) { const modelEvents = attrs.modelEvents ? attrs.modelEvents : null const normalizedEvents = Array.isArray(modelEvents) ? modelEvents.join(' ') : modelEvents watch( () => props.modelValue, (val, prevVal) => { setValue(editor, val, prevVal) } ) watch( () => props.value, (val, prevVal) => { setValue(editor, val, prevVal) }, { immediate: true } ) editor.on(normalizedEvents || 'change keyup undo redo', () => { const content = editor.getContent({format: attrs.outputFormat}) emits('update:modelValue', content) emits('change', content) }) editor.on('FullscreenStateChanged', (e) => { fullscreen.value = e.state }) } function handleImageUploading(name) { const editor = unref(editorRef) if (!editor) { return } editor.execCommand('mceInsertContent', false, getUploadingImgName(name)) const content = editor?.getContent() ?? '' setValue(editor, content) } function handleDone(name, url) { const editor = unref(editorRef) if (!editor) { return } const content = editor?.getContent() ?? '' const val = content?.replace(getUploadingImgName(name), `<img src="${url}"/>`) ?? '' setValue(editor, val) } function getUploadingImgName(name) { return `[uploading:${name}]` } </script> <style lang="scss" scoped> .prefixCls { position: relative; line-height: normal; } textarea { z-index: -1; visibility: hidden; } </style>
这里要注意下 Tinymce.vue 组件中的 tinymce.ts 和 onMountedOrActivated 导入的路径
使用 tinymce 1 2 <tinymce v-model="description" @change="onChagneThiymce" width="100%"/>
1 2 3 4 5 6 7 8 9 import Tinymce from "/@/components/tinymce/Tinymce.vue" const description = ref("" )const onChagneThiymce = (val ) => { console .log(val) form.value.description = val }
使用阿里云的视频点播服务 开通视频点播功能 首先打开控制台,找到相关服务进行开通
文档地址 https://help.aliyun.com/document_detail/57723.html
安装 在 service 包下新建一个子包 service_vod,然后导入下面的依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <dependency > <groupId > com.aliyun</groupId > <artifactId > aliyun-java-sdk-core</artifactId > <version > 4.6.0</version > </dependency > <dependency > <groupId > com.aliyun</groupId > <artifactId > aliyun-java-sdk-vod</artifactId > <version > 2.16.5</version > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > fastjson</artifactId > <version > 1.2.83</version > </dependency > <dependency > <groupId > com.aliyun</groupId > <artifactId > aliyun-java-sdk-kms</artifactId > <version > 2.10.1</version > </dependency >
初始化client 1 2 3 4 5 6 7 8 9 10 11 public class InitClientTest { public static DefaultAcsClient initVodClient () throws ClientException { String regionId = "cn-shanghai" ; String accessKeyId = "your oss key" ; String accessKeySecret = "your oss keysecret" ; DefaultProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret); DefaultAcsClient client = new DefaultAcsClient(profile); return client; } }
获取视频播放地址 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 import com.aliyuncs.DefaultAcsClient;import com.aliyuncs.exceptions.ClientException;import com.aliyuncs.vod.model.v20170321.GetPlayInfoRequest;import com.aliyuncs.vod.model.v20170321.GetPlayInfoResponse;import java.util.List;public class GetVoidUrlTest { public static GetPlayInfoResponse getPlayInfo (DefaultAcsClient client) throws Exception { GetPlayInfoRequest request = new GetPlayInfoRequest(); request.setVideoId("20dc391742c34a69a4a708f167df52f4" ); return client.getAcsResponse(request); } public static void main (String[] args) throws ClientException { DefaultAcsClient client = InitClientTest.initVodClient(); GetPlayInfoResponse response = new GetPlayInfoResponse(); try { response = getPlayInfo(client); List<GetPlayInfoResponse.PlayInfo> playInfoList = response.getPlayInfoList(); for (GetPlayInfoResponse.PlayInfo playInfo : playInfoList) { System.out.print("播放地址 = " + playInfo.getPlayURL() + "\n" ); } System.out.print("视频名称 = " + response.getVideoBase().getTitle() + "\n" ); } catch (Exception e) { System.out.print("ErrorMessage = " + e.getLocalizedMessage()); } System.out.print("RequestId = " + response.getRequestId() + "\n" ); } }
运行结果
获取视频播放凭证 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import com.aliyuncs.DefaultAcsClient;import com.aliyuncs.exceptions.ClientException;import com.aliyuncs.vod.model.v20170321.GetVideoPlayAuthRequest;import com.aliyuncs.vod.model.v20170321.GetVideoPlayAuthResponse;public class GetVideoPlayAuthTest { public static GetVideoPlayAuthResponse getVideoPlayAuth (DefaultAcsClient client) throws Exception { GetVideoPlayAuthRequest request = new GetVideoPlayAuthRequest(); request.setVideoId("20dc391742c34a69a4a708f167df52f4" ); return client.getAcsResponse(request); } public static void main (String[] argv) throws ClientException { DefaultAcsClient client = InitClientTest.initVodClient(); GetVideoPlayAuthResponse response = new GetVideoPlayAuthResponse(); try { response = getVideoPlayAuth(client); System.out.println("播放凭证 = " + response.getPlayAuth()); System.out.println("视频名称 = " + response.getVideoMeta().getTitle()); } catch (Exception e) { System.out.println("ErrorMessage = " + e.getLocalizedMessage()); } System.out.println("RequestId = " + response.getRequestId()); } }
运行结果
上传视频到阿里云 导入上传所需的依赖 首先下载官方提供的jar包和示例代码:下载地址
下载后将 lib 文件夹下的 aliyun-java-vod-upload-1.4.14.jar 添加到自己的项目中
然后继续导入如下的依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 <dependency > <groupId > com.aliyun</groupId > <artifactId > aliyun-java-sdk-core</artifactId > <version > 4.5.1</version > </dependency > <dependency > <groupId > com.aliyun.oss</groupId > <artifactId > aliyun-sdk-oss</artifactId > <version > 3.10.2</version > </dependency > <dependency > <groupId > com.aliyun</groupId > <artifactId > aliyun-java-sdk-vod</artifactId > <version > 2.15.11</version > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > fastjson</artifactId > <version > 1.2.28</version > </dependency > <dependency > <groupId > org.json</groupId > <artifactId > json</artifactId > <version > 20170516</version > </dependency > <dependency > <groupId > com.google.code.gson</groupId > <artifactId > gson</artifactId > <version > 2.8.2</version > </dependency > <dependency > <groupId > com.aliyun.vod</groupId > <artifactId > upload</artifactId > <version > 1.4.14</version > <scope > system</scope > <systemPath > D:\mygitee\00-Java全套知识学习\Java实战案例\java_product_guli\guli_parent\service\service_vod\lis\aliyun-java-vod-upload-1.4.14.jar </systemPath > </dependency >
上传本地视频到阿里云 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import com.aliyun.vod.upload.impl.UploadVideoImpl;import com.aliyun.vod.upload.req.UploadVideoRequest;import com.aliyun.vod.upload.resp.UploadVideoResponse;public class VoidUploadTest { public static void main (String[] args) { String accessKeyId = "your oss key" ; String accessKeySecret = "your oss keysecret" ; String title = "上传的文件名称" ; String fileName = "D:\\工具\\EV录屏视频\\20220921_105611.mp4" ; UploadVideoRequest request = new UploadVideoRequest(accessKeyId, accessKeySecret, title, fileName); request.setPartSize(2 * 1024 * 1024L ); request.setTaskNum(1 ); UploadVideoImpl uploader = new UploadVideoImpl(); UploadVideoResponse response = uploader.uploadVideo(request); System.out.print("RequestId=" + response.getRequestId() + "\n" ); if (response.isSuccess()) { System.out.print("VideoId=" + response.getVideoId() + "\n" ); } else { System.out.print("VideoId=" + response.getVideoId() + "\n" ); System.out.print("ErrorCode=" + response.getCode() + "\n" ); System.out.print("ErrorMessage=" + response.getMessage() + "\n" ); } } }
添加视频上传接口 添加启动类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package com.szx.vod;import lombok.SneakyThrows;import lombok.extern.log4j.Log4j2;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;import org.springframework.context.ConfigurableApplicationContext;import org.springframework.context.annotation.ComponentScan;import org.springframework.core.env.Environment;import java.net.InetAddress;@Log4j2 @SpringBootApplication(exclude = DataSourceAutoConfiguration.class) @ComponentScan(basePackages = {"com.szx"}) public class VodApplication { @SneakyThrows public static void main (String[] args) { ConfigurableApplicationContext application = SpringApplication.run(VodApplication.class, args); Environment env = application.getEnvironment(); String host = InetAddress.getLocalHost().getHostAddress(); String port = env.getProperty("server.port" ); log.info("\n ----------------------------------------------------------\n\t" + "Application '{}' 正在运行中... Access URLs:\n\t" + "Local: \t\thttp://localhost:{}\n\t" + "External: \thttp://{}:{}\n\t" + "Doc: \thttp://{}:{}/doc.html\n\t" + "SwaggerDoc: \thttp://{}:{}/swagger-ui.html\n\t" + "----------------------------------------------------------" , env.getProperty("spring.application.name" ), env.getProperty("server.port" ), host, port, host, port, host, port); } }
配置文件 1 2 3 4 5 6 7 8 9 10 11 12 13 server.port =8003 spring.application.name =service_vod spring.profiles.active =dev aliyun.oss.file.keyid =your oss key aliyun.oss.file.keysecret =your oss keysecret spring.servlet.multipart.max-file-size =1000MB spring.servlet.multipart.max-request-size =1000MB
utils 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package com.szx.vod.utils;import org.springframework.beans.factory.InitializingBean;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Component;@Component public class VodUtils implements InitializingBean { @Value("${aliyun.oss.file.keyid}") private String keyid; @Value("${aliyun.oss.file.keysecret}") private String keysecret; public static String KEYID; public static String KEYSECRET; @Override public void afterPropertiesSet () throws Exception { KEYID = keyid; KEYSECRET = keysecret; } }
service 接口
1 2 3 4 5 6 7 8 9 10 11 package com.szx.vod.service;import org.springframework.web.multipart.MultipartFile;public interface VodService { String vodUpload (MultipartFile file) ; }
实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package com.szx.vod.service.impl;import com.aliyun.vod.upload.impl.UploadVideoImpl;import com.aliyun.vod.upload.req.UploadStreamRequest;import com.aliyun.vod.upload.resp.UploadStreamResponse;import com.szx.vod.service.VodService;import com.szx.vod.utils.VodUtils;import lombok.SneakyThrows;import org.springframework.stereotype.Service;import org.springframework.web.multipart.MultipartFile;import java.io.InputStream;@Service public class VodServiceImpl implements VodService { @SneakyThrows @Override public String vodUpload (MultipartFile file) { String fileName = file.getOriginalFilename(); String title = fileName.substring(0 ,fileName.lastIndexOf("." )); InputStream inputStream = file.getInputStream(); UploadStreamRequest request = new UploadStreamRequest(VodUtils.KEYID, VodUtils.KEYSECRET, title, fileName, inputStream); UploadVideoImpl uploader = new UploadVideoImpl(); UploadStreamResponse response = uploader.uploadStream(request); System.out.print("RequestId=" + response.getRequestId() + "\n" ); if (response.isSuccess()) { System.out.print("VideoId=" + response.getVideoId() + "\n" ); return response.getVideoId(); } else { System.out.print("VideoId=" + response.getVideoId() + "\n" ); System.out.print("ErrorCode=" + response.getCode() + "\n" ); System.out.print("ErrorMessage=" + response.getMessage() + "\n" ); return response.getVideoId(); } } }
controller 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.szx.vod.controller;import com.szx.commonutils.Msg;import com.szx.vod.service.VodService;import io.swagger.annotations.Api;import io.swagger.annotations.ApiOperation;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.CrossOrigin;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.multipart.MultipartFile;@Api(tags = "视频上传模块") @CrossOrigin @RestController @RequestMapping("/vodservice") public class VodController { @Autowired VodService vodService; @ApiOperation("视频上传") @PostMapping("vodUpload") public Msg vodUpload (MultipartFile file) { String vodId = vodService.vodUpload(file); return Msg.Ok().data("vodId" ,vodId); } }
swagger测试接口
查看视频点播控制台上传成功
视频删除 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public Boolean deleteVod (String id) { DefaultAcsClient client = InitClient.initVodClient(); try { deleteVideo(client,id); } catch (Exception e) { throw new GuliException(500 ,"视频删除失败" ); } return true ; } public static DeleteVideoResponse deleteVideo (DefaultAcsClient client, String id) throws Exception { DeleteVideoRequest request = new DeleteVideoRequest(); request.setVideoIds(id); return client.getAcsResponse(request); }
什么是微服务 微服务的由来 微服务最早由Martin Fowler与James Lewis于2014年共同提出,微服务架构风格是一种使用一套小服务来 开发单个应用的方式途径,每个服务运行在自己的进程中,并使用轻量级机制通信,通常是HTTP API, 这些服务基于业务能力构建,并能够通过自动化部署机制来独立部署,这些服务使用不同的编程语言实 现,以及不同数据存储技术,并保持最低限度的集中式管理。
为什么需要微服务 在传统的IT行业软件大多都是各种独立系统的堆砌,这些系统的问题总结来说就是扩展性差,可靠性不 高,维护成本高。到后面引入了SOA服务化,但是,由于 SOA 早期均使用了总线模式,这种总线模式是 与某种技术栈强绑定的,比如:J2EE。这导致很多企业的遗留系统很难对接,切换时间太长,成本太 高,新系统稳定性的收敛也需要一些时间
微服务和单体架构的区别 (1)单体架构所有的模块全都耦合在一块,代码量大,维护困难。 微服务每个模块就相当于一个单独的项目,代码量明显减少,遇到问题也相对来说比较好解决。
(2)单体架构所有的模块都共用一个数据库,存储方式比较单一。 微服务每个模块都可以使用不同的存储方式(比如有的用redis,有的用mysql等),数据库也是单 个模块对应自己的数据库。
(3)单体架构所有的模块开发所使用的技术一样。 微服务每个模块都可以使用不同的开发技术,开发模式更灵活
微服务的本质 (1)微服务,关键其实不仅仅是微服务本身,而是系统要提供一套基础的架构,这种架构使得微服务可 以独立的部署、运行、升级,不仅如此,这个系统架构还让微服务与微服务之间在结构上“松耦合”,而在 功能上则表现为一个统一的整体。这种所谓的“统一的整体”表现出来的是统一风格的界面,统一的权限管 理,统一的安全策略,统一的上线过程,统一的日志和审计方法,统一的调度方式,统一的访问入口等 等。
(2)微服务的目的是有效的拆分应用,实现敏捷开发和部署 。
(3)微服务提倡的理念团队间应该是 inter-operate, not integrate 。inter-operate是定义好系统的边界 和接口,在一个团队内全栈,让团队自治,原因就是因为如果团队按照这样的方式组建,将沟通的成本 维持在系统内部,每个子系统就会更加内聚,彼此的依赖耦合能变弱,跨系统的沟通成本也就能降低
什么样的项目适合微服务 微服务可以按照业务功能本身的独立性来划分,如果系统提供的业务是非常底层的,如:操作系统内 核、存储系统、网络系统、数据库系统等等,这类系统都偏底层,功能和功能之间有着紧密的配合关 系,如果强制拆分为较小的服务单元,会让集成工作量急剧上升,并且这种人为的切割无法带来业务上 的真正的隔离,所以无法做到独立部署和运行,也就不适合做成微服务了
微服务开发框架 目前微服务的开发框架,最常用的有以下四个:
Spring Cloud:http://projects.spring.io/spring-cloud(现在非常流行的微服务架构)
Dubbo:http://dubbo.io
Dropwizard:http://www.dropwizard.io (关注单个微服务的开发)
Consul、etcd&etc.(微服务的模块)
什么是SpringClound Spring Cloud是一系列框架的集合。它利用Spring Boot的开发便利性简化了分布式系统基础设施的开 发,如服务发现、服务注册、配置中心、消息总线、负载均衡、 熔断器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring并没有重复制造轮子,它只是将目前各家公司开发的比较 成熟、经得起实际考验的服务框架组合起来,通过SpringBoot风格进行再封装屏蔽掉了复杂的配置和实 现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包
Spring Cloud和Spring Boot是什么关系 Spring Boot 是 Spring 的一套快速配置脚手架,可以基于Spring Boot 快速开发单个微服务,Spring Cloud是一个基于Spring Boot实现的开发工具;Spring Boot专注于快速、方便集成的单个微服务个 体,Spring Cloud关注全局的服务治理框架; Spring Boot使用了默认大于配置的理念,很多集成方案已 经帮你选择好了,能不配置就不配置,Spring Cloud很大的一部分是基于Spring Boot来实现,必须基 于Spring Boot开发。可以单独使用Spring Boot开发项目,但是Spring Cloud离不开 Spring Boot
Spring Cloud相关基础服务组件 服务发现——Netflix Eureka (Nacos)
服务调用——Netflix Feign
熔断器——Netflix Hystrix
服务网关——Spring Cloud GateWay
分布式配置——Spring Cloud Config (Nacos)
消息总线 —— Spring Cloud Bus (Nacos)
Nacos 基本概念 (1)Nacos 是阿里巴巴推出来的一个新开源项目,是一个更易于构建云原生应用的动态服务发现、配置 管理和服务管理平台。Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特 性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。Nacos 帮助您更敏捷和容易 地构建、交付和管理微服务平台。 Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原 生范式) 的服务基础设施。
(2)常见的注册中心:
Eureka(原生,2.0遇到性能瓶颈,停止维护)
Zookeeper(支持,专业的独立产品。例如:dubbo)
Consul(原生,GO语言开发)
Nacos 相对于 Spring Cloud Eureka 来说,Nacos 更强大。Nacos = Spring Cloud Eureka + Spring Cloud Config Nacos 可以与 Spring, Spring Boot, Spring Cloud 集成,并能代替 Spring Cloud Eureka, Spring Cloud Config - 通过 Nacos Server 和 spring-cloud-starter-alibaba-nacos-discovery 实现服务的注册与发现。
(3)Nacos是以服务为主要服务对象的中间件,Nacos支持所有主流的服务发现、配置和管理。 Nacos主要提供以下四大功能:
服务发现和服务健康监测
动态配置服务
动态DNS服务
服务及其元数据管理
Nacos下载和安 (1)下载地址和版本
下载地址:https://github.com/alibaba/nacos/releases
下载版本:nacos-server-1.1.4.tar.gz或nacos-server-1.1.4.zip,解压任意目录即可
(2)启动nacos服务
Linux/Unix/Mac 启动命令(standalone代表着单机模式运行,非集群模式) 启动命令:sh startup.sh -m standalone
windows 启动命令:cmd startup.cmd 或者双击startup.cmd运行文件。 访问:http://localhost:8848/nacos 用户名密码:nacos/nacos
服务注册 添加依赖
1 2 3 4 5 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-discovery</artifactId > </dependency >
配置文件添加配置
1 2 spring.cloud.nacos.discovery.server-addr =127.0.0.1:8848
在启动类上添加一个注解
启动已注册的微服务,可以在Nacos服务列表中看到被注册的微服务
Feign 概念
Feign是Netflix开发的声明式、模板化的HTTP客户端, Feign可以帮助我们更快捷、优雅地调 用HTTP API
Feign支持多种注解,例如Feign自带的注解或者JAX-RS注解等。
Spring Cloud对Feign进行了增强,使Feign支持了Spring MVC注解,并整合了Ribbon和Eureka,从 而让Feign的使用更加方便。
Spring Cloud Feign是基于Netflix feign实现,整合了Spring Cloud Ribbon和Spring Cloud Hystrix, 除了提供这两者的强大功能外,还提供了一种声明式的Web服务客户端定义的方式。
Spring Cloud Feign帮助我们定义和实现依赖服务接口的定义。在Spring Cloud feign的实现下,只需 要创建一个接口并用注解方式配置它,即可完成服务提供方的接口绑定,简化了在使用Spring Cloud Ribbon时自行封装服务调用客户端的开发量
实现服务调用 需求:我们在删除课程时同时删除视频。这就要求在 edu 服务中调用 vod 服务。
首先我们吧 edu 也在注册中心进行服务注册
添加服务调用依赖
1 2 3 4 5 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-openfeign</artifactId > </dependency >
在调用端的启动类上添加依赖
创建 client 包和 VodClient 接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package com.szx.edu.client;import com.szx.commonutils.Msg;import org.springframework.cloud.openfeign.FeignClient;import org.springframework.stereotype.Component;import org.springframework.web.bind.annotation.DeleteMapping;import org.springframework.web.bind.annotation.PathVariable;@FeignClient("service-vod") @Component public interface VodClient { @DeleteMapping("/vodservice/deleteVod/{id}") Msg deleteVod (@PathVariable("id") String id) ; }
然后再删除课程时调用该接口方法,Spring Clound 会自动的帮我们调用 vod 服务中的删除视频方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 @Autowired CourseDescriptionServiceImpl courseDescriptionService; @Autowired ChapterService chapterService; @Autowired VideoService videoService; @Autowired VodClient vodClient; public Boolean removeCourse (String id) { boolean b = this .removeById(id); boolean b1 = courseDescriptionService.removeById(id); QueryWrapper<Chapter> chapterQueryWrapper = new QueryWrapper<>(); chapterQueryWrapper.eq("course_id" ,id); chapterService.remove(chapterQueryWrapper); QueryWrapper<Video> videoQueryWrapper = new QueryWrapper<>(); videoQueryWrapper.eq("course_id" ,id); List<Video> videoList = videoService.list(videoQueryWrapper); videoList.forEach(video -> { String videoSourceId = video.getVideoSourceId(); if (StringUtils.isNotEmpty(videoSourceId)){ vodClient.deleteVod(videoSourceId); } }); videoService.remove(videoQueryWrapper); return b; }
hystrix熔断器 添加依赖
1 2 3 4 5 6 7 8 9 10 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-ribbon</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-hystrix</artifactId > </dependency >
在调用端的配置文件中添加如下配置
1 2 3 4 feign.hystrix.enabled =true hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds =6000
然后添加熔断器的实现类,就是client接口的实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 @FeignClient(value = "service-vod",fallback = VodClientImpl.class) @Component public interface VodClient { @DeleteMapping("/vodservice/deleteVod/{id}") Msg deleteVod (@PathVariable("id") String id) ; }
服务端渲染技术NUXT 什么是服务端渲染 服务端渲染又称SSR (Server Side Render)是在服务端完成页面的内容,而不是在客户端通过AJAX获取数据。
服务器端渲染(SSR)的优势主要在于:更好的 SEO,由于搜索引擎爬虫抓取工具可以直接查看完全渲染的 页面。
如果你的应用程序初始展示 loading 菊花图,然后通过 Ajax 获取内容,抓取工具并不会等待异步完成后 再进行页面内容的抓取。也就是说,如果 SEO 对你的站点至关重要,而你的页面又是异步获取内容,则 你可能需要服务器端渲染(SSR)解决此问题
另外,使用服务器端渲染,我们可以获得更快的内容到达时间(time-to-content),无需等待所有的 JavaScript 都完成下载并执行,产生更好的用户体验,对于那些「内容到达时间(time-to-content)与转化 率直接相关」的应用程序而言,服务器端渲染( SSR)至关重要。
什么是Nuxt Nuxt.js 是一个基于 Vue.js 的轻量级应用框架,可用来创建服务端渲染 (SSR) 应用,也可充当静态站点引擎 生成静态站点应用,具有优雅的代码结构分层和热加载等特性。
官网网站:
https://zh.nuxtjs.org/
添加redis缓存 添加依赖 首先添加依赖
1 2 3 4 5 6 7 8 9 10 11 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-redis</artifactId > </dependency > <dependency > <groupId > org.apache.commons</groupId > <artifactId > commons-pool2</artifactId > <version > 2.6.0</version > </dependency >
添加配置类 在 service-base 中添加 RedisConfig
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 package com.szx.servicebase.config;import com.fasterxml.jackson.annotation.JsonAutoDetect;import com.fasterxml.jackson.annotation.PropertyAccessor;import com.fasterxml.jackson.databind.ObjectMapper;import org.springframework.cache.CacheManager;import org.springframework.cache.annotation.CachingConfigurerSupport;import org.springframework.cache.annotation.EnableCaching;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.cache.RedisCacheConfiguration;import org.springframework.data.redis.cache.RedisCacheManager;import org.springframework.data.redis.connection.RedisConnectionFactory;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;import org.springframework.data.redis.serializer.RedisSerializationContext;import org.springframework.data.redis.serializer.RedisSerializer;import org.springframework.data.redis.serializer.StringRedisSerializer;import java.time.Duration;@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); template.setKeySerializer(redisSerializer); template.setValueSerializer(jackson2JsonRedisSerializer); template.setHashValueSerializer(jackson2JsonRedisSerializer); return template; } @Bean public CacheManager cacheManager (RedisConnectionFactory factory) { 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); 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; } }
Spring Boot缓存注解 缓存@Cacheable 根据方法对其返回结果进行缓存,下次请求时,如果缓存存在,则直接读取缓存数据返回;如果缓存不 存在,则执行方法,并把返回的结果存入缓存中。一般用在查询方法上
缓存@CachePut 使用该注解标志的方法,每次都会执行,并将结果存入指定的缓存中。其他方法可以直接从响应的缓存 中读取缓存数据,而不需要再去查询数据库。一般用在新增方法上
缓存@CacheEvict 使用该注解标志的方法,会清空指定的缓存。一般用在更新或者删除方法上
添加redis缓存使用 首先添加配置类
1 2 3 4 5 6 7 8 9 spring.redis.host =127.0.0.1 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 spring.redis.lettuce.pool.max-idle =5 spring.redis.lettuce.pool.min-idle =0
然后再 serviceImpl 类的方法上添加注解
1 2 3 4 5 6 7 8 9 @Cacheable(value = "banner",key = "'selectIndexList'") public List<CrmBanner> getBannerList () { QueryWrapper<CrmBanner> qw = new QueryWrapper<>(); qw.orderByDesc("gmt_create" ); qw.last("limit 2" ); List<CrmBanner> crmBannerList = this .list(qw); return crmBannerList; }
查看保存到 Redis 的 key
单点登录机制 概念 在多个服务中,用户只需要登陆一次,就可以在访问多个服务
单点登录的三种方式
整合JWT 添加依赖 1 2 3 4 5 6 7 8 <dependencies > <dependency > <groupId > io.jsonwebtoken</groupId > <artifactId > jjwt</artifactId > </dependency > </dependencies >
添加工具类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 package com.szx.commonutils;import io.jsonwebtoken.Claims;import io.jsonwebtoken.Jws;import io.jsonwebtoken.Jwts;import io.jsonwebtoken.SignatureAlgorithm;import org.springframework.http.server.reactive.ServerHttpRequest;import org.springframework.util.StringUtils;import javax.servlet.http.HttpServletRequest;import java.util.Date;public class JwtUtils { public static final long EXPIRE = 1000 * 60 * 60 * 24 ; public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO" ; public static String getJwtToken (String id, String nickname) { String JwtToken = Jwts.builder() .setHeaderParam("typ" , "JWT" ) .setHeaderParam("alg" , "HS256" ) .setSubject("guli-user" ) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRE)) .claim("id" , id) .claim("nickname" , nickname) .signWith(SignatureAlgorithm.HS256, APP_SECRET) .compact(); return JwtToken; } public static boolean checkToken (String jwtToken) { if (StringUtils.isEmpty(jwtToken)) return false ; try { Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken); } catch (Exception e) { e.printStackTrace(); return false ; } return true ; } 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 ; } 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" ); } }
整合阿里云短信服务 安装依赖 1 2 3 4 5 6 <dependency > <groupId > com.aliyun</groupId > <artifactId > aliyun-java-sdk-dysmsapi</artifactId > <version > 2.2.1</version > </dependency >
添加controller调用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 package com.szx.oss.controller;import com.alibaba.nacos.client.naming.utils.RandomUtils;import com.baomidou.mybatisplus.core.toolkit.StringUtils;import com.szx.commonutils.Msg;import com.szx.oss.service.impl.MsmServiceImpl;import com.szx.oss.utils.RandomUtil;import io.swagger.annotations.Api;import org.bouncycastle.pqc.math.linearalgebra.RandUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.web.bind.annotation.*;import java.util.HashMap;import java.util.concurrent.TimeUnit;@Api(tags = "短信服务") @CrossOrigin @RestController @RequestMapping("/eduoss/msm") public class MsmController { @Autowired MsmServiceImpl msmService; @Autowired RedisTemplate<String,String> redisTemplate; @GetMapping("send/{phone}") public Msg send (@PathVariable("phone") String phone) { try { String code = redisTemplate.opsForValue().get(phone); if (StringUtils.isNotEmpty(code)){ return Msg.Ok(); }else { String codes = RandomUtil.getFourBitRandom(); HashMap<String, String> map = new HashMap<>(); map.put("code" ,codes); redisTemplate.opsForValue().set(phone,codes,5 , TimeUnit.MINUTES); msmService.send(phone,map); } } catch (Exception exception) { exception.printStackTrace(); } return Msg.Ok(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 package com.szx.oss.service.impl;import com.alibaba.fastjson.JSONObject;import com.aliyun.oss.ClientException;import com.aliyuncs.DefaultAcsClient;import com.aliyuncs.IAcsClient;import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;import com.aliyuncs.exceptions.ServerException;import com.aliyuncs.profile.DefaultProfile;import com.google.gson.Gson;import com.szx.oss.service.MsmService;import com.szx.oss.utils.OssPropertyUtils;import org.springframework.stereotype.Service;import java.util.HashMap;@Service public class MsmServiceImpl implements MsmService { public void send (String phone, HashMap<String,String> codeMap) throws Exception { DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou" , OssPropertyUtils.KEYID, OssPropertyUtils.KEYSECRET); IAcsClient client = new DefaultAcsClient(profile); SendSmsRequest request = new SendSmsRequest(); request.setSignName("阿里云短信测试" ); request.setTemplateCode("SMS_154950909" ); request.setPhoneNumbers(phone); request.setTemplateParam(JSONObject.toJSONString(codeMap)); try { SendSmsResponse response = client.getAcsResponse(request); System.out.println(new Gson().toJson(response)); } catch (ServerException e) { e.printStackTrace(); } catch (ClientException e) { System.out.println("RequestId:" + e.getRequestId()); } } }
登录接口开发 这里对前端传过来的密码使用MD5进行加密,然后和数据库中的密码进行匹配。来判断登录密码是否正确
MD5工具类代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package com.szx.educenter.utils;import java.security.MessageDigest;import java.security.NoSuchAlgorithmException;public final class MD5 { public static String encrypt (String strSrc) { try { char hexChars[] = { '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , 'a' , 'b' , 'c' , 'd' , 'e' , 'f' }; byte [] bytes = strSrc.getBytes(); MessageDigest md = MessageDigest.getInstance("MD5" ); md.update(bytes); bytes = md.digest(); int j = bytes.length; char [] chars = new char [j * 2 ]; int k = 0 ; for (int i = 0 ; i < bytes.length; i++) { byte b = bytes[i]; chars[k++] = hexChars[b >>> 4 & 0xf ]; chars[k++] = hexChars[b & 0xf ]; } return new String(chars); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); throw new RuntimeException("MD5加密出错!!+" + e); } } public static void main (String[] args) { System.out.println(MD5.encrypt("111111" )); } }
登录接口代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Override public String login (UcenterMember ucenterMember) { if (StringUtils.isEmpty(ucenterMember.getMobile())){ throw new GuliException(500 ,"请输入手机号" ); } if (StringUtils.isEmpty(ucenterMember.getPassword())){ throw new GuliException(500 ,"请输入密码" ); } QueryWrapper<UcenterMember> qw = new QueryWrapper<>(); qw.eq("mobile" ,ucenterMember.getMobile()); UcenterMember member = this .getOne(qw); if (member == null ){ throw new GuliException(500 ,"账号不存在" ); } if (!MD5.encrypt(ucenterMember.getPassword()).equals(member.getPassword())){ throw new GuliException(500 ,"密码不正确" ); } return JwtUtils.getJwtToken(member.getId(), member.getNickname()); }
验证登录方法,登录成功后会返回一个token
注册接口开发 首先编写一个用于注册的VO类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Data public class RegisterUcenter { @ApiModelProperty(value = "手机号") private String mobile; @ApiModelProperty(value = "密码") private String password; @ApiModelProperty(value = "昵称") private String nickname; @ApiModelProperty(value = "验证码") private String code; }
然后编写对应的controller
1 2 3 4 5 6 @PostMapping("register") @ApiModelProperty("登录") public Msg register (@RequestBody RegisterUcenter registerUcenter) { ucenterMemberService.register(registerUcenter); return Msg.Ok(); }
编写对应 server 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public void register (RegisterUcenter registerUcenter) { String nickname = registerUcenter.getNickname(); String password = registerUcenter.getPassword(); String mobile = registerUcenter.getMobile(); String code = registerUcenter.getCode(); if (StringUtils.isEmpty(nickname) || StringUtils.isEmpty(password) || StringUtils.isEmpty(mobile) || StringUtils.isEmpty(code)) { throw new GuliException("请将信息填写完整" ); } QueryWrapper<UcenterMember> qw = new QueryWrapper<>(); qw.eq("mobile" ,mobile); int count = this .count(qw); if (count > 0 ){ throw new GuliException("该用户已存在,请勿重复注册" ); } String redisCode = redisTemplate.opsForValue().get(mobile); if (!code.equals(redisCode)){ throw new GuliException("请输入正确的验证码" ); } UcenterMember ucenterMember = new UcenterMember(); ucenterMember.setMobile(mobile); ucenterMember.setNickname(nickname); ucenterMember.setPassword(MD5.encrypt(password)); ucenterMember.setAvatar("https://songzx0106.github.io/images/favicon.png" ); ucenterMember.setIsDisabled(false ); this .save(ucenterMember); }
在注册时有用到短信验证码,需要先发送短信,然后短信会保存在 Redis 中,在注册的时候去判断用户输入的验证码和 Redis 中保存的验证码是否一致,如果不一致则表示验证码输入错误或者验证码过期。
在经过一系列的判断后都没有问题,则将信息保存在数据库中即可。
整合微信扫码登录 获取微信key 首先在资源文件中添加微信相关key,这里要用实际公司中的
1 2 3 wx.open.appid=wxed9954c01bb89b47 wx.open.appsecret=a7482517235173ddb4083788de60b90e wx.open.redirecturl=http://localhost:8160/api/ucenter/wx/callback
添加工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package com.szx.educenter.utils;import org.springframework.beans.factory.InitializingBean;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Component;@Component public class WxAppidUtils implements InitializingBean { @Value("${wx.open.appid}") private String appId; @Value("${wx.open.appsecret}") private String appSecret; @Value("${wx.open.redirecturl}") private String redirectUrl; public static String WX_OPEN_APP_ID; public static String WX_OPEN_APP_SECRET; public static String WX_OPEN_REDIRECT_URL; @Override public void afterPropertiesSet () throws Exception { WX_OPEN_APP_ID = appId; WX_OPEN_APP_SECRET = appSecret; WX_OPEN_REDIRECT_URL = redirectUrl; } }
添加controller 用户请求这个接口后使浏览器重定向到微信提供的地址,直接生成一个登录二维码。
注意这个 controller 不能使用 @RestController 注解,要使用 @Controller 注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 package com.szx.educenter.controller;import com.szx.educenter.utils.WxAppidUtils;import com.szx.servicebase.exceptionhandler.GuliException;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.CrossOrigin;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.io.UnsupportedEncodingException;import java.net.URLEncoder;@CrossOrigin @Controller @RequestMapping("/wx/open") public class WxLoginController { @GetMapping("/login") public String wxLogin () { String baseUrl = "https://open.weixin.qq.com/connect/qrconnect" + "?appid=%s" + "&redirect_uri=%s" + "&response_type=code" + "&scope=snsapi_login" + "&state=%s" + "#wechat_redirect" ; String redirectUrl = WxAppidUtils.WX_OPEN_REDIRECT_URL; try { redirectUrl = URLEncoder.encode(redirectUrl, "UTF-8" ); } catch (UnsupportedEncodingException e) { throw new GuliException(20001 , e.getMessage()); } String qrcodeUrl = String.format(baseUrl, WxAppidUtils.WX_OPEN_APP_ID, redirectUrl, "imhelen" ); return "redirect:" + qrcodeUrl; } }
然后启动服务,访问 /wx/open/login,然后浏览器自动跳转页面,出现如下二维码
实现扫码后的回调方法 扫码后微信会回调我们配置好的回调方法,同时携带code和state信息
我们通过code获取access_token和openid
得到openid后去数据库查询对应的用户信息
查不到,根据access_token和openid请求固定的接口获取用户信息并保存到数据库中
可以查到,将用户信息查询出来
将查询到的用户id和nickname封装成token,通过路径传参重定向前端页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 @GetMapping("callback") public String callback (String code,String state) { String baseAccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token" + "?appid=%s" + "&secret=%s" + "&code=%s" + "&grant_type=authorization_code" ; String accessTokenUrl = String.format(baseAccessTokenUrl, WxAppidUtils.WX_OPEN_APP_ID, WxAppidUtils.WX_OPEN_APP_SECRET, code); String openid = "" ; try { String accessResInfo = HttpClientUtils.get(accessTokenUrl); Gson gson = new Gson(); HashMap<String,Object> hashMap = gson.fromJson(accessResInfo,HashMap.class); String accessToken = (String)hashMap.get("access_token" ); openid = (String)hashMap.get("openid" ); UcenterMember byOpenid = memberService.getUserInfoByOpenid(openid); if (byOpenid == null ){ String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" + "?access_token=%s" + "&openid=%s" ; String userInfoUrl = String.format(baseUserInfoUrl, accessToken, openid); String resultUserInfo = null ; try { resultUserInfo = HttpClientUtils.get(userInfoUrl); } catch (Exception e) { throw new GuliException(20001 , "获取用户信息失败" ); } HashMap<String, Object> mapUserInfo = gson.fromJson(resultUserInfo,HashMap.class); String nickname = (String)mapUserInfo.get("nickname" ); String headimgurl = (String)mapUserInfo.get("headimgurl" ); byOpenid = new UcenterMember(); byOpenid.setOpenid(openid); byOpenid.setNickname(nickname); byOpenid.setAvatar(headimgurl); memberService.save(byOpenid); } String jwtToken = JwtUtils.getJwtToken(byOpenid.getId(), byOpenid.getNickname()); return "redirect:http://192.168.3.120:8011/?jwtToken=" + jwtToken; } catch (Exception e) { e.printStackTrace(); } return null ; }
前端根据路径token显示登录信息 1 2 3 4 5 6 7 8 9 let jwtToken = this .$route.query.jwtToken;if (jwtToken) { cookie.set("guli_token" , jwtToken); getLoginInfo().then((info ) => { cookie.set("userinfo" , JSON .stringify(info.data.info)); this .userInfo = info.data.info; }); }
效果展示
安装Jenkins 基础环境准备 在 Linux 系统中安装好如下内容
Java JDK1.8安装 通过宝塔面板安装 java 项目管理器即可
输入 java -version
查看版本
Git安装
然后通过 git --version
来检查是否安装成功
Maven安装 首先打开官网下载 gz 包
Index of /maven/maven-3/3.6.3/binaries (apache.org)
选择 apache-maven-3.6.1-bin.tar.gz 包下载
然后上传到 /usr/local 目录中
在该目录下执行解压操作
1 tar -zxvf apache-maven-3.6.3-bin.tar.gz
修改环境变量
在文件结尾输入下面的代码
1 2 export MAVEN_HOME=/usr/local /apache-maven-3.6.3export PATH=$PATH :$MAVEN_HOME /bin
然后按下 Esc,输入 :wq
保存并退出,如果输入有误,可以输入 :q!
不保存并退出
通过命令source /etc/profile让profile文件立即生效
然后输入 mvn -v
查看版本
安装Docker 通过宝塔安装
输入命令查看 docker 版本
Jenkins安装 上传并启动 打开这个地址,下载 war 包,打开下面的地址下载最新的即可
Index of /war (jenkins.io)
将下载的 war 包上传到 usr/local/jenkins
然后 cd 到 usr/local/jenkins
输入命令启动 Jenkins
1 nohup java -jar jenkins.war --httpPort=8088
这里注意要提前吧 8088 端口开放
查看进程
查看所有进程
杀死指定 pid 的进程
查看当前运行的端口
通过ip加端口号访问 输入 ip:8088
进行访问,出现下面的界面表示启动成功
查看并输入密码 我们通过 cat 命令查看上面图片给出的文件内容,即是密码
配置国内镜像 输入密码后,点击继续,稍等片刻会出现下面的图
这里不要不要急着点击安装,需要先去配置一下国内的镜像,然后再来点击。否则会安装失败
这里我们先关掉浏览器,打开linux系统进行操作
首先关闭的 Jenkins 进程
然后进入 Jenkins 目录,并查看文件夹下的文件
1 2 3 cd /root/.jenkins/updates/ls
在该目录下执行如下命令
1 sed -i 's/http:\/\/updates.jenkins-ci.org\/download/https:\/\/mirrors.tuna.tsinghua.edu.cn\/jenkins/g' default.json && sed -i 's/http:\/\/www.google.com/https:\/\/www.baidu.com/g' default.json
这是直接修改的配置文件,如果前边 Jenkins 用 sudo 启动的话,那么这里的两个 sed 前均需要加上 sudo
运行后没有报错就表示执行成功
然后重启 Jenkins,需要再次输入密码
上图表示正在安装中,但是都是错误,没关系,我们等待进度条完成后点击继续即可
创建账户 我这里账户名和密码都是 songzx
实例配置默认,点击保存并完成
进入Jenkins首页
环境配置 配置java jdk 点击首页的 Manage Jenkins
选择全局工具配置
查询 jdk 命令
配置git git 配置的是路径,通过下面的命令查询
配置Maven