├── src ├── main │ ├── resources │ │ ├── config.properties │ │ ├── static │ │ │ ├── img │ │ │ │ ├── 11.jpg │ │ │ │ ├── 222.jpg │ │ │ │ ├── 444.jpg │ │ │ │ ├── 555.jpg │ │ │ │ ├── back.jpg │ │ │ │ ├── cat.jpg │ │ │ │ └── background.jpg │ │ │ ├── js │ │ │ │ └── app.js │ │ │ ├── css │ │ │ │ ├── _custom.scss │ │ │ │ ├── login.css │ │ │ │ ├── common.css │ │ │ │ └── _variables.scss │ │ │ ├── login.html │ │ │ ├── updatePassword.html │ │ │ ├── phoneLogin.html │ │ │ ├── missPassword.html │ │ │ ├── userUpdate.html │ │ │ ├── register.html │ │ │ ├── problemManage.html │ │ │ ├── problemInsert.html │ │ │ ├── problemUpdate.html │ │ │ ├── userManage.html │ │ │ ├── index.html │ │ │ └── problemDetail.html │ │ ├── application.properties │ │ └── mapper │ │ │ ├── ProblemMapper.xml │ │ │ └── UserMapper.xml │ ├── ceshi │ │ ├── 初步页面设计.png │ │ ├── 在线OJ平台.png │ │ └── 测试用例.md │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── demo │ │ │ ├── pojo │ │ │ ├── Question.java │ │ │ ├── JsonRequest.java │ │ │ ├── Problem.java │ │ │ ├── Answer.java │ │ │ ├── User.java │ │ │ ├── TencentCloudUser.java │ │ │ └── JsonResult.java │ │ │ ├── service │ │ │ ├── SendSmsService.java │ │ │ ├── TaskService.java │ │ │ ├── UserService.java │ │ │ └── impl │ │ │ │ ├── UserServiceImpl.java │ │ │ │ ├── TaskServiceImpl.java │ │ │ │ └── SendSmsServiceImpl.java │ │ │ ├── config │ │ │ ├── JavaConfig.java │ │ │ ├── InterceptorConfig.java │ │ │ └── RedisConfig.java │ │ │ ├── DemoApplication.java │ │ │ ├── exception │ │ │ ├── ServiceException.java │ │ │ ├── DeleteException.java │ │ │ ├── UpdateException.java │ │ │ ├── InsertException.java │ │ │ ├── UserNotFoundException.java │ │ │ ├── ProblemNotFountException.java │ │ │ ├── PasswordNotMatchException.java │ │ │ ├── UsernameNotFoundException.java │ │ │ └── UsernameDuplicatedException.java │ │ │ ├── mapper │ │ │ ├── UserMapper.java │ │ │ └── ProblemMapper.java │ │ │ ├── interceptor │ │ │ ├── ModeLoadInterceptor.java │ │ │ └── LoginInterceptor.java │ │ │ ├── util │ │ │ ├── FileUtil.java │ │ │ └── CommandUtil.java │ │ │ └── controller │ │ │ ├── ProblemController.java │ │ │ ├── BaseController.java │ │ │ ├── CompileController.java │ │ │ ├── UserManageController.java │ │ │ ├── ProblemManageController.java │ │ │ └── UserController.java │ └── db │ │ ├── db.sql │ │ └── test.sql └── test │ └── java │ └── com │ └── example │ └── demo │ ├── FileTest.java │ ├── GetPassWord.java │ ├── TaskTest.java │ ├── Solution.java │ └── DemoApplicationTests.java ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ ├── maven-wrapper.properties │ └── MavenWrapperDownloader.java ├── .gitignore └── pom.xml /src/main/resources/config.properties: -------------------------------------------------------------------------------- 1 | secretId= 2 | secretKey= -------------------------------------------------------------------------------- /src/main/ceshi/初步页面设计.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WorldBlueSky/OJ_Project-SpringBoot/HEAD/src/main/ceshi/初步页面设计.png -------------------------------------------------------------------------------- /src/main/ceshi/在线OJ平台.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WorldBlueSky/OJ_Project-SpringBoot/HEAD/src/main/ceshi/在线OJ平台.png -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WorldBlueSky/OJ_Project-SpringBoot/HEAD/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /src/main/resources/static/img/11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WorldBlueSky/OJ_Project-SpringBoot/HEAD/src/main/resources/static/img/11.jpg -------------------------------------------------------------------------------- /src/main/resources/static/img/222.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WorldBlueSky/OJ_Project-SpringBoot/HEAD/src/main/resources/static/img/222.jpg -------------------------------------------------------------------------------- /src/main/resources/static/img/444.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WorldBlueSky/OJ_Project-SpringBoot/HEAD/src/main/resources/static/img/444.jpg -------------------------------------------------------------------------------- /src/main/resources/static/img/555.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WorldBlueSky/OJ_Project-SpringBoot/HEAD/src/main/resources/static/img/555.jpg -------------------------------------------------------------------------------- /src/main/resources/static/img/back.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WorldBlueSky/OJ_Project-SpringBoot/HEAD/src/main/resources/static/img/back.jpg -------------------------------------------------------------------------------- /src/main/resources/static/img/cat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WorldBlueSky/OJ_Project-SpringBoot/HEAD/src/main/resources/static/img/cat.jpg -------------------------------------------------------------------------------- /src/main/resources/static/img/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WorldBlueSky/OJ_Project-SpringBoot/HEAD/src/main/resources/static/img/background.jpg -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/pojo/Question.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.pojo; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | // 这个类作为一个Task的参数,包含要编译的代码 8 | @Data 9 | @AllArgsConstructor 10 | @NoArgsConstructor 11 | public class Question { 12 | private String code; 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/com/example/demo/FileTest.java: -------------------------------------------------------------------------------- 1 | package com.example.demo; 2 | 3 | import com.example.demo.util.FileUtil; 4 | import org.junit.Test; 5 | 6 | public class FileTest { 7 | 8 | @Test 9 | public void test(){ 10 | FileUtil.writeFile("./hello.txt","你好世界!"); 11 | System.out.println(FileUtil.readFile("./hello.txt")); 12 | } 13 | 14 | 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/service/SendSmsService.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.service; 2 | 3 | public interface SendSmsService { 4 | 5 | /** 6 | * @param phoneNum 短信发送的手机号 7 | * @param templateCode 使用的短信模板id 8 | * @param code 发送的手机验证码 9 | * @return 返回是否发送成功 10 | */ 11 | 12 | boolean send(String phoneNum,String templateCode,String code); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/pojo/JsonRequest.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.pojo; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class JsonRequest { 11 | 12 | // 用来接收前端传递过来的 JS对象转成JSON格式字符串 的数据,接收JSON对象 13 | private String id; 14 | private String code; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/service/TaskService.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.service; 2 | 3 | import com.example.demo.pojo.Answer; 4 | import com.example.demo.pojo.Question; 5 | 6 | public interface TaskService { 7 | 8 | /** 9 | * 将完整的java代码进行编译运行的方法 10 | * @param question 用户提交的代码与测试用例代码拼接成最终的代码 11 | * @return 返回编译或者运行的最终结果(错误码,错误原因,标准输出) 12 | */ 13 | 14 | Answer compileAndRun(Question question); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/** 5 | !**/src/test/** 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | 30 | ### VS Code ### 31 | .vscode/ 32 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/pojo/Problem.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.pojo; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.io.Serializable; 8 | 9 | @Data 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | public class Problem implements Serializable { 13 | private int id; 14 | private String title; 15 | private String level; 16 | private String description; 17 | private String template; 18 | private String testCode; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/config/JavaConfig.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.config; 2 | 3 | import org.springframework.context.annotation.ComponentScan; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.context.annotation.PropertySource; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Configuration 9 | @PropertySource("classpath:config.properties") // 加载资源文件 10 | @ComponentScan(basePackages = "com.example.demo.pojo") // 扫描组件,将属性替换 11 | public class JavaConfig { 12 | 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/DemoApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.demo; 2 | 3 | import com.example.demo.pojo.Problem; 4 | import org.mybatis.spring.annotation.MapperScan; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | 8 | @SpringBootApplication 9 | @MapperScan(basePackages = "com.example.demo.mapper") 10 | public class DemoApplication { 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(DemoApplication.class, args); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/pojo/Answer.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.pojo; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | // 表示一个task的执行结果 8 | @Data 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | public class Answer { 12 | //约定error 0--编译运行ok 1--编译出错 2--运行出错 13 | private int error; 14 | // 如果编译出错,reason放编译出错信息,如果运行错误,放运行异常的信息 15 | private String reason; 16 | // 如果编译运行都ok的话,放运行程序获取标准输出的结果 17 | private String stdout; 18 | //放标准错误 19 | private String stderr; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/pojo/User.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.pojo; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import org.apache.ibatis.type.Alias; 7 | import org.springframework.context.annotation.ComponentScan; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.io.Serializable; 11 | 12 | @Data 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | public class User implements Serializable { 16 | private int id; 17 | private String username; 18 | private String password; 19 | private int isAdmin; // 0是普通用户 1是管理员 20 | private String salt; 21 | private String email; // 邮箱可设可不设,如果设置了邮箱,那么可以根据邮箱找回密码 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/exception/ServiceException.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.exception; 2 | 3 | public class ServiceException extends Exception{ 4 | public ServiceException() { 5 | super(); 6 | } 7 | 8 | public ServiceException(String message) { 9 | super(message); 10 | } 11 | 12 | public ServiceException(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | 16 | public ServiceException(Throwable cause) { 17 | super(cause); 18 | } 19 | 20 | protected ServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 21 | super(message, cause, enableSuppression, writableStackTrace); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/exception/DeleteException.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.exception; 2 | 3 | public class DeleteException extends ServiceException { 4 | public DeleteException() { 5 | super(); 6 | } 7 | 8 | public DeleteException(String message) { 9 | super(message); 10 | } 11 | 12 | public DeleteException(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | 16 | public DeleteException(Throwable cause) { 17 | super(cause); 18 | } 19 | 20 | protected DeleteException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 21 | super(message, cause, enableSuppression, writableStackTrace); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/exception/UpdateException.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.exception; 2 | 3 | public class UpdateException extends ServiceException { 4 | public UpdateException() { 5 | super(); 6 | } 7 | 8 | public UpdateException(String message) { 9 | super(message); 10 | } 11 | 12 | public UpdateException(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | 16 | public UpdateException(Throwable cause) { 17 | super(cause); 18 | } 19 | 20 | protected UpdateException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 21 | super(message, cause, enableSuppression, writableStackTrace); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/pojo/TencentCloudUser.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.pojo; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import org.springframework.beans.factory.InitializingBean; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | public class TencentCloudUser implements InitializingBean { 10 | @Value("${secretId}") 11 | private String secretId; 12 | @Value("${secretKey}") 13 | private String secretKey; 14 | 15 | public static String ID; 16 | public static String KEY; 17 | 18 | 19 | @Override 20 | public void afterPropertiesSet() throws Exception { 21 | ID=this.secretId; 22 | KEY=this.secretKey; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/exception/InsertException.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.exception; 2 | 3 | /** 4 | * 数据在插入过程中所产生的异常 5 | */ 6 | public class InsertException extends ServiceException { 7 | public InsertException() { 8 | super(); 9 | } 10 | 11 | public InsertException(String message) { 12 | super(message); 13 | } 14 | 15 | public InsertException(String message, Throwable cause) { 16 | super(message, cause); 17 | } 18 | 19 | public InsertException(Throwable cause) { 20 | super(cause); 21 | } 22 | 23 | protected InsertException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 24 | super(message, cause, enableSuppression, writableStackTrace); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/exception/UserNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.exception; 2 | 3 | public class UserNotFoundException extends ServiceException{ 4 | public UserNotFoundException() { 5 | super(); 6 | } 7 | 8 | public UserNotFoundException(String message) { 9 | super(message); 10 | } 11 | 12 | public UserNotFoundException(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | 16 | public UserNotFoundException(Throwable cause) { 17 | super(cause); 18 | } 19 | 20 | protected UserNotFoundException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 21 | super(message, cause, enableSuppression, writableStackTrace); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/com/example/demo/GetPassWord.java: -------------------------------------------------------------------------------- 1 | package com.example.demo; 2 | 3 | import com.example.demo.service.UserService; 4 | import com.example.demo.service.impl.UserServiceImpl; 5 | import org.junit.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | 9 | @SpringBootTest 10 | public class GetPassWord { 11 | 12 | 13 | 14 | @Test 15 | public void test(){ 16 | String password = "123456"; 17 | String salt ="abcdefg"; 18 | String finalPassword = UserServiceImpl.getMd5Password(password,salt); 19 | System.out.println("原密码为:"+password); 20 | System.out.println("盐值为: "+salt); 21 | System.out.println("数据库保存密码为:"+finalPassword); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/exception/ProblemNotFountException.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.exception; 2 | 3 | public class ProblemNotFountException extends ServiceException{ 4 | public ProblemNotFountException() { 5 | super(); 6 | } 7 | 8 | public ProblemNotFountException(String message) { 9 | super(message); 10 | } 11 | 12 | public ProblemNotFountException(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | 16 | public ProblemNotFountException(Throwable cause) { 17 | super(cause); 18 | } 19 | 20 | protected ProblemNotFountException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 21 | super(message, cause, enableSuppression, writableStackTrace); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/com/example/demo/TaskTest.java: -------------------------------------------------------------------------------- 1 | package com.example.demo; 2 | 3 | import com.example.demo.pojo.Answer; 4 | import com.example.demo.pojo.Question; 5 | import com.example.demo.service.impl.TaskServiceImpl; 6 | import org.junit.Test; 7 | 8 | public class TaskTest { 9 | 10 | @Test 11 | public void test(){ 12 | TaskServiceImpl task = new TaskServiceImpl(); 13 | Question question = new Question(); 14 | question.setCode("public class Solution {\n" + 15 | "\n" + 16 | " public static void main(String[] args) {\n" + 17 | " System.out.println(\"hello world!\");\n" + 18 | " }\n" + 19 | "}"); 20 | Answer answer = task.compileAndRun(question); 21 | System.out.println(answer); 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/exception/PasswordNotMatchException.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.exception; 2 | 3 | /** 4 | * 密码匹配失败的异常 5 | */ 6 | public class PasswordNotMatchException extends ServiceException{ 7 | public PasswordNotMatchException() { 8 | super(); 9 | } 10 | 11 | public PasswordNotMatchException(String message) { 12 | super(message); 13 | } 14 | 15 | public PasswordNotMatchException(String message, Throwable cause) { 16 | super(message, cause); 17 | } 18 | 19 | public PasswordNotMatchException(Throwable cause) { 20 | super(cause); 21 | } 22 | 23 | protected PasswordNotMatchException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 24 | super(message, cause, enableSuppression, writableStackTrace); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/exception/UsernameNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.exception; 2 | 3 | /** 4 | * 用户名未找到的异常,用户数据不存在 5 | */ 6 | public class UsernameNotFoundException extends ServiceException{ 7 | public UsernameNotFoundException() { 8 | super(); 9 | } 10 | 11 | public UsernameNotFoundException(String message) { 12 | super(message); 13 | } 14 | 15 | public UsernameNotFoundException(String message, Throwable cause) { 16 | super(message, cause); 17 | } 18 | 19 | public UsernameNotFoundException(Throwable cause) { 20 | super(cause); 21 | } 22 | 23 | protected UsernameNotFoundException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 24 | super(message, cause, enableSuppression, writableStackTrace); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/db/db.sql: -------------------------------------------------------------------------------- 1 | create database if not exists oj_database; 2 | 3 | use oj_database; 4 | 5 | drop table if exists oj_table; 6 | 7 | create table oj_table( 8 | id int primary key auto_increment comment '题目的id,自增主键', 9 | title varchar(50) not null comment '题目的标题', 10 | level varchar(20) comment '题目的难度', 11 | description varchar(4096) comment '题目描述', 12 | template varchar(4096) comment '编程的初始模板', 13 | testCode varchar(4096) comment '编程题目的测试用例' 14 | ); 15 | 16 | drop table if exists user; 17 | 18 | create table user( 19 | id int primary key auto_increment comment '用户id', 20 | username varchar(20) not null unique comment '用户名', 21 | password varchar(4096) comment '用户密码', 22 | isAdmin int default 0 comment '是否是管理员用户', 23 | salt varchar(1024) comment '盐值', 24 | email varchar(50) unique comment '邮箱用于找回个人密码' 25 | ); 26 | 27 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/exception/UsernameDuplicatedException.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.exception; 2 | 3 | 4 | /** 5 | * 表示用户名被占用的异常 6 | */ 7 | public class UsernameDuplicatedException extends ServiceException{ 8 | public UsernameDuplicatedException() { 9 | super(); 10 | } 11 | 12 | public UsernameDuplicatedException(String message) { 13 | super(message); 14 | } 15 | 16 | public UsernameDuplicatedException(String message, Throwable cause) { 17 | super(message, cause); 18 | } 19 | 20 | public UsernameDuplicatedException(Throwable cause) { 21 | super(cause); 22 | } 23 | 24 | protected UsernameDuplicatedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 25 | super(message, cause, enableSuppression, writableStackTrace); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.service; 2 | 3 | import com.example.demo.exception.InsertException; 4 | import com.example.demo.exception.PasswordNotMatchException; 5 | import com.example.demo.exception.UsernameDuplicatedException; 6 | import com.example.demo.exception.UsernameNotFoundException; 7 | import com.example.demo.pojo.User; 8 | 9 | public interface UserService { 10 | 11 | /** 12 | * 用户注册方法 13 | * @param user 用户的数据对象 14 | */ 15 | 16 | void register(User user) throws UsernameDuplicatedException, InsertException; //register注册方法 17 | 18 | /** 19 | * 用户登陆功能 20 | * @param username 用户名 21 | * @param password 用户密码 22 | * @return 当前匹配的用户数据,如果没有则返回null值 23 | */ 24 | 25 | User login(String username,String password) throws UsernameNotFoundException, PasswordNotMatchException; 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/pojo/JsonResult.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.pojo; 2 | 3 | import lombok.*; 4 | 5 | import java.io.Serializable; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class JsonResult implements Serializable { 11 | 12 | public Integer state;//响应的状态码 13 | public String message;// 返回信息 14 | public E data;//返回的数据,因为不确定返回什么类型,所以返回泛型即可,类也是泛型 15 | 16 | // /** 只传递state类型的数据 **/ 17 | // public JsonResult(Integer state) { 18 | // this.status = state; 19 | // } 20 | // 21 | // /* 传递state,以及返回对应的数据 */ 22 | // public JsonResult(Integer state, E data) { 23 | // this.status = state; 24 | // this.data = data; 25 | // } 26 | // 27 | // /** 28 | // * 返回出异常的提示信息 29 | // * @param e 30 | // */ 31 | // public JsonResult(Throwable e) { 32 | // this.message = e.getMessage(); 33 | // } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/resources/static/js/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Charcoal by @attacomsian (https://twitter.com) 3 | * Copyright 2017-2018 4 | */ 5 | $(function () { 6 | 7 | // init tooltip & popovers 8 | $('[data-toggle="tooltip"]').tooltip(); 9 | $('[data-toggle="popover"]').popover(); 10 | 11 | //page scroll 12 | $('a.page-scroll').bind('click', function (event) { 13 | var $anchor = $(this); 14 | $('html, body').stop().animate({ 15 | scrollTop: $($anchor.attr('href')).offset().top - 100 16 | }, 1500); 17 | event.preventDefault(); 18 | }); 19 | 20 | //toggle scroll menu 21 | $(window).scroll(function () { 22 | var scroll = $(window).scrollTop(); 23 | if (scroll >= 400) { 24 | $('.sticky-navigation').addClass('bg-primary'); 25 | } else { 26 | $('.sticky-navigation').removeClass('bg-primary'); 27 | } 28 | return false; 29 | }); 30 | 31 | }); -------------------------------------------------------------------------------- /src/main/java/com/example/demo/mapper/UserMapper.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.mapper; 2 | 3 | import com.example.demo.pojo.User; 4 | import org.apache.ibatis.annotations.Mapper; 5 | import org.apache.ibatis.annotations.Param; 6 | import org.springframework.beans.factory.annotation.Value; 7 | 8 | import java.util.HashMap; 9 | import java.util.List; 10 | 11 | @Mapper 12 | public interface UserMapper { 13 | 14 | int insert(User user) ; 15 | 16 | int delete(@Param("id") int id); 17 | 18 | List selectAll(); 19 | 20 | User selectById(int id); 21 | 22 | User selectByName(String username); 23 | 24 | User selectByNameAndId(String id,String username); 25 | 26 | // 用户名模糊查询 27 | List selectByLikeName(String likeName); 28 | 29 | User selectByEmail(String email); 30 | 31 | int updateUser(@Param("id") int id,@Param("username") String username,@Param("email") String email); 32 | 33 | int updatePassword(@Param("username") String username, @Param("finalPassword") String finalPassword); 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/mapper/ProblemMapper.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.mapper; 2 | 3 | import com.example.demo.pojo.Problem; 4 | import org.apache.ibatis.annotations.Mapper; 5 | import org.apache.ibatis.annotations.Param; 6 | 7 | import java.util.List; 8 | 9 | @Mapper 10 | public interface ProblemMapper { 11 | // 实现增删改查 12 | 13 | //1、增加题目 14 | int insert(Problem problem); 15 | 16 | //2、根据id删除题目 17 | int delete(@Param("id") int id); 18 | 19 | //3、查询所有的题目 20 | List selectAll(); 21 | 22 | //4、查询一个题目 23 | Problem selectOne(@Param("id") int id); 24 | 25 | //5、查询题目根据名字、模糊查询 26 | List selectByLikeTitle(String likeTitle); 27 | 28 | //6、根据id修改题目信息 29 | int updateById(@Param("id") int id, 30 | @Param("title") String title, 31 | @Param("level") String level, 32 | @Param("description") String description, 33 | @Param("template") String template, 34 | @Param("testCode") String testCode); 35 | 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/interceptor/ModeLoadInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.interceptor; 2 | 3 | import com.example.demo.pojo.User; 4 | import org.springframework.web.servlet.HandlerInterceptor; 5 | 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpServletResponse; 8 | import javax.servlet.http.HttpSession; 9 | 10 | /** 11 | * 这里的定义的拦截器是为了 拦截访问后台管理模块网站的 用户 12 | */ 13 | 14 | public class ModeLoadInterceptor implements HandlerInterceptor { 15 | @Override 16 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 17 | HttpSession session = request.getSession(false); 18 | if(session==null){ 19 | response.sendRedirect("login.html"); 20 | return false; 21 | } 22 | 23 | User user = (User)session.getAttribute("user"); 24 | if(user==null || user.getIsAdmin()==0){ 25 | response.sendRedirect("index.html"); 26 | return false; 27 | } 28 | 29 | return true; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/resources/static/css/_custom.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Created by Kroplet (https://www.kroplet.com) 3 | * The easiest way to create Bootstrap 4 themes. 4 | */ 5 | 6 | @import url('https://fonts.googleapis.com/css?family=Barlow:300,400,600'); 7 | 8 | .dropdown-menu.show[aria-labelledby="theme-components"] { 9 | min-width: 17rem; 10 | } 11 | 12 | .dropdown-menu.show[aria-labelledby="theme-components"] .dropdown-item { 13 | width: 49%; 14 | display: inline-block; 15 | } 16 | 17 | .vh-100 { 18 | height: 100vh; 19 | } 20 | 21 | .bg-hero { 22 | background-color: $dark; 23 | background-image: url(../img/charcoal.jpg); 24 | background-size: cover; 25 | background-position: center center; 26 | position: relative; 27 | } 28 | 29 | .bg-hero:before { 30 | position: absolute; 31 | z-index: 1; 32 | width: 100%; 33 | height: 100%; 34 | display: block; 35 | left: 0; 36 | top: 0; 37 | content: ""; 38 | background-color: rgba(0, 0, 0, 0.6); 39 | } 40 | 41 | .bg-hero .container { 42 | z-index: 2; 43 | position: relative; 44 | } 45 | 46 | .btn.btn-radius { 47 | border-radius: 30px; 48 | padding: 0.5rem 1.5rem; 49 | } -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # 应用名称 2 | spring.application.name=demo 3 | # 应用服务 WEB 访问端口 4 | server.port=9090 5 | 6 | #下面这些内容是为了让MyBatis映射 7 | 8 | #指定Mybatis的Mapper文件 9 | mybatis.mapper-locations=classpath:mapper/*.xml 10 | #指定Mybatis的实体目录 11 | mybatis.type-aliases-package=com.example.demo.pojo 12 | 13 | # 配置Mysql数据库相 14 | spring.datasource.type=com.zaxxer.hikari.HikariDataSource 15 | spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 16 | spring.datasource.name=defaultDataSource 17 | spring.datasource.url=jdbc:mysql://localhost:3306/oj_database?serverTimezone=UTC 18 | spring.datasource.username=root 19 | spring.datasource.password=123456 20 | 21 | #配置redis 22 | spring.redis.host=127.0.0.1 23 | spring.redis.port=6379 24 | spring.redis.password=123456 25 | 26 | #热部署生效(true为开启,false为不开启,开发者根据喜好选择是否开启) 27 | spring.devtools.restart.enabled=true 28 | 29 | #一般使用SMTP服务器 简单邮件传输协议进行发送邮件 30 | spring.mail.host=smtp.qq.com 31 | #端口号 32 | spring.mail.port=587 33 | #发送邮件的邮箱地址:改成自己的邮箱 34 | spring.mail.username=2745131427@qq.com 35 | #发送短信后它给你的授权码 填写到这里 36 | spring.mail.password=lxvhobowcipndfdj 37 | #与发件邮箱一致qq.com,用于之后的字符串替换 38 | spring.mail.from=2745131427@qq.com 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/util/FileUtil.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.util; 2 | 3 | import org.springframework.stereotype.Service; 4 | 5 | import java.io.*; 6 | 7 | public class FileUtil { 8 | 9 | // 使用字符流读写文件 10 | 11 | // 读文件的内容到内存中的操作 12 | public static String readFile(String filePath){// 将filePath中的内容读取出来 13 | 14 | StringBuilder sb = new StringBuilder(); 15 | try(FileReader fileReader = new FileReader(filePath)){ 16 | 17 | while(true){ 18 | int ch = fileReader.read(); 19 | if(ch==-1){ 20 | break; 21 | } 22 | sb.append((char) ch); 23 | } 24 | return sb.toString(); 25 | 26 | } catch (IOException e) { 27 | e.printStackTrace(); 28 | } 29 | 30 | return null; 31 | } 32 | 33 | // 从内存中读信息写入到文件当中的操作 34 | public static void writeFile(String filePath,String content){// 将content中的内容写入到filePath中的文件中 35 | try( FileWriter fileWriter = new FileWriter(filePath)) { 36 | fileWriter.write(content); 37 | } catch (IOException e) { 38 | e.printStackTrace(); 39 | } 40 | } 41 | 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/resources/mapper/ProblemMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | insert into oj_table values (0,#{title},#{level},#{description},#{template},#{testCode}) 10 | 11 | 12 | 13 | update oj_table set title=#{title},level=#{level}, 14 | description=#{description},template=#{template}, 15 | testCode=#{testCode} where id=#{id} 16 | 17 | 18 | 19 | delete from oj_table where id=#{id} 20 | 21 | 22 | 25 | 26 | 29 | 30 | 31 | 34 | 35 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/interceptor/LoginInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.interceptor; 2 | 3 | import org.springframework.web.servlet.HandlerInterceptor; 4 | import org.springframework.web.servlet.ModelAndView; 5 | 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpServletResponse; 8 | import javax.servlet.http.HttpSession; 9 | 10 | public class LoginInterceptor implements HandlerInterceptor { 11 | 12 | /** 13 | * 14 | * @param request 请求 15 | * @param response 响应 16 | * @param handler 处理器 17 | * @return 18 | * @throws Exception 19 | */ 20 | @Override 21 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 22 | 23 | // 获取session对象 24 | HttpSession session = request.getSession(false); 25 | //System.out.println("session:"+session); 26 | 27 | // session在用户名登陆的时候设置了 "user"->user 28 | // 在手机号验证码登陆的时候设置了 "phone"->phone 29 | if(session==null || (session.getAttribute("user")==null && session.getAttribute("phone")==null)){ 30 | // 上面的逻辑一定要搞清楚,user 和 phone 的信息,session中有一个即可 31 | //System.out.println("进入拦截器,重定向到登陆界面!"); 32 | response.sendRedirect("login.html"); 33 | return false;// 禁止通行 34 | } 35 | // 放行 36 | return true; 37 | } 38 | 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/config/InterceptorConfig.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.config; 2 | 3 | import com.example.demo.interceptor.LoginInterceptor; 4 | import com.example.demo.interceptor.ModeLoadInterceptor; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 7 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 8 | 9 | @Configuration 10 | public class InterceptorConfig implements WebMvcConfigurer { 11 | 12 | /** 13 | * 将自定义的拦截器加入到 mvc 框架中 14 | */ 15 | @Override 16 | public void addInterceptors(InterceptorRegistry registry) { 17 | // 将登录拦截注册到 配置中 18 | registry.addInterceptor(new LoginInterceptor()) 19 | .addPathPatterns("/index.html", 20 | "/problemDetail.html", 21 | "/problemInsert.html", 22 | "/problemManage.html", 23 | "/problemUpdate.html", 24 | "/userManage.html", 25 | "/userUpdate.html");// 必须加上/ 26 | 27 | // 将用户身份权限拦截 注册到 配置中 28 | registry.addInterceptor(new ModeLoadInterceptor()) 29 | .addPathPatterns("/userManage.html", 30 | "/userUpdate.html", 31 | "/problemManage.html", 32 | "/problemInsert.html", 33 | "/problemUpdate.html");// 对后台管理的模块的网页设置身份验证拦截 34 | 35 | } 36 | 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/com/example/demo/Solution.java: -------------------------------------------------------------------------------- 1 | package com.example.demo; 2 | class Solution { 3 | public int[] twoSum(int[] nums, int target) { 4 | int[] a = new int[2]; 5 | for(int i=0;i 2 | 5 | 6 | 7 | 8 | 9 | 10 | insert into user(id, username, password,isAdmin,salt,email) values (0,#{username},#{password},#{isAdmin},#{salt},#{email}) 11 | 12 | 13 | 14 | 15 | 16 | update user set username=#{username},email=#{email} where id=#{id} 17 | 18 | 19 | update user set password=#{finalPassword} where username=#{username} 20 | 21 | 22 | 23 | 24 | delete from user where id=#{id} 25 | 26 | 27 | 30 | 31 | 34 | 37 | 40 | 41 | 44 | 45 | 48 | 49 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/controller/ProblemController.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.controller; 2 | 3 | import com.example.demo.exception.ProblemNotFountException; 4 | import com.example.demo.mapper.ProblemMapper; 5 | import com.example.demo.pojo.Problem; 6 | import com.fasterxml.jackson.core.JsonProcessingException; 7 | import com.fasterxml.jackson.databind.ObjectMapper; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.web.bind.annotation.CrossOrigin; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RequestParam; 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | import java.util.List; 15 | 16 | @RestController 17 | @RequestMapping("/oj") 18 | public class ProblemController extends BaseController { 19 | 20 | @Autowired 21 | public ProblemMapper problemMapper; 22 | 23 | @Autowired 24 | public ObjectMapper objectMapper; 25 | 26 | 27 | /** 28 | * 获取 数据库中 题目列表、题目详情的接口 29 | * @param id 30 | * @return 31 | * @throws ProblemNotFountException 32 | * @throws JsonProcessingException 33 | */ 34 | @RequestMapping(value = "/problems",produces = "application/json;charset=utf-8") 35 | public String getProblems(@RequestParam(value = "id",required = false) String id) throws ProblemNotFountException, JsonProcessingException { 36 | 37 | if(id==null || id.equals("")){// 如果没有参数的话,那么直接查询全部题目 38 | List list = problemMapper.selectAll(); 39 | return objectMapper.writeValueAsString(list);//前端接收的是JSON格式的数据,所以要将list转化成 json格式的字符串 40 | } 41 | 42 | // 如果有参数的话,那么查询指定id的题目详情 43 | int idString = Integer.parseInt(id); 44 | Problem problem = problemMapper.selectOne(idString); 45 | 46 | if(problem==null){ 47 | throw new ProblemNotFountException(); 48 | } 49 | 50 | return objectMapper.writeValueAsString(problem); 51 | 52 | } 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/db/test.sql: -------------------------------------------------------------------------------- 1 | # 下面的sql语句用于 插入数据库中的具体信息 2 | 3 | use oj_database; 4 | 5 | # 设置题目的信息 6 | 7 | insert into oj_table values(0,'两数之和','简单','给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出和为目标值target的那两个整数,并返回它们的数组下标。\n\n你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。\n\n你可以按任意顺序返回答案。\n\n 示例 1:\n\n 输入:nums = [2,7,11,15], target = 9\n输出:[0,1]\n解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1]。\n\n示例 2:\n\n输入:nums = [3,2,4], target = 6\n输出:[1,2]\n\n示例 3:\n\n输入:nums = [3,3], target = 6\n输出:[0,1]\n\n\n来源:力扣(LeetCode)\n\n链接:https://leetcode.cn/problems/two-sum\n\n著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。', 8 | 'class Solution { 9 | public int[] twoSum(int[] nums, int target) { 10 | 11 | } 12 | }','public static void main(String[] args) { 13 | Solution solution = new Solution(); 14 | //testcase1 15 | //nums = [2,7,11,15], target = 9 16 | //[0,1] 17 | int[] nums1 = {2,7,11,15}; 18 | int target = 9; 19 | int[] result1 = solution.twoSum(nums1,target); 20 | if(result1.length==2 && result1[0]==0 && result1[1]==1){ 21 | System.out.println("testcase1 OK!"); 22 | }else{ 23 | System.out.println("testcase1 Failed!"); 24 | } 25 | //nums = [3,2,4], target = 6 26 | //[1,2] 27 | int[] nums2 = {3,2,4}; 28 | target=6; 29 | int[] result2 = solution.twoSum(nums2,target); 30 | if(result2.length==2 && result2[0]==1 && result2[1]==2){ 31 | System.out.println("testcase2 OK!"); 32 | }else{ 33 | System.out.println("testcase2 Failed!"); 34 | } 35 | //输入:nums = [3,3], target = 6 36 | //输出:[0,1] 37 | int[] nums3 = {3,3}; 38 | target=6; 39 | int[] result3 = solution.twoSum(nums3,target); 40 | if(result3.length==2 && result3[0]==0 && result3[1]==1){ 41 | System.out.println("testcase3 OK!"); 42 | }else{ 43 | System.out.println("testcase3 Failed!"); 44 | } 45 | }'); 46 | 47 | # 设置一个管理员账户 48 | #默认密码是 123456,盐值为 abcdefg 49 | 50 | insert into user(id,username,password,isAdmin,salt) values (0,'admin','87AD2B76937B34C58891913DED4C6DAC',1,'abcdefg'); -------------------------------------------------------------------------------- /src/main/java/com/example/demo/util/CommandUtil.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.util; 2 | 3 | import java.io.FileOutputStream; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | 7 | public class CommandUtil { 8 | 9 | // 创建子进程,执行指定命令 10 | 11 | /** 12 | * 13 | * @param cmd 子进程执行的命令 14 | * @param stdoutFile 标准输出 写入的文件路径 15 | * @param stderrFile 标准错误 写入的文件路径 16 | * @return 返回进程的状态码 17 | */ 18 | 19 | public static int run(String cmd,String stdoutFile,String stderrFile){ 20 | try { 21 | //1、通过Runtime类,执行exec 22 | Process process = Runtime.getRuntime().exec(cmd); 23 | 24 | //2、获取到标准输出 ,写入到指定文件中 25 | // 如果为null的话,忽略标准输出 26 | if(stdoutFile!=null){ 27 | InputStream stdoutFrom = process.getInputStream();//从内存中读取内容的流 28 | FileOutputStream stdoutTo = new FileOutputStream(stdoutFile);//往文件中写入内容的流 29 | while(true){ 30 | int ch = stdoutFrom.read(); 31 | if(ch==-1){ 32 | break; 33 | } 34 | stdoutTo.write(ch); 35 | } 36 | // 关闭资源 37 | stdoutFrom.close(); 38 | stdoutTo.close(); 39 | } 40 | 41 | //3、获取到标准错误,写入到指定文件中 42 | // 如果为null,忽略标准错误 43 | if(stderrFile!=null){ 44 | InputStream stderrFrom = process.getErrorStream();//从内存中读取内容的流 45 | FileOutputStream stderrTo = new FileOutputStream(stderrFile);//往文件中写入内容的流 46 | while(true){ 47 | int ch = stderrFrom.read(); 48 | if(ch==-1){ 49 | break; 50 | } 51 | stderrTo.write(ch); 52 | } 53 | // 关闭资源 54 | stderrFrom.close(); 55 | stderrTo.close(); 56 | } 57 | 58 | //4、等待子进程结束,拿到子进程的状态码,并返回 59 | int exitCode = process.waitFor(); 60 | return exitCode; 61 | 62 | } catch (IOException | InterruptedException e) { 63 | e.printStackTrace(); 64 | } 65 | return 0; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/config/RedisConfig.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.config; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAutoDetect; 4 | import com.fasterxml.jackson.annotation.PropertyAccessor; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.ComponentScan; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.context.annotation.PropertySource; 12 | import org.springframework.data.redis.connection.RedisConnectionFactory; 13 | import org.springframework.data.redis.core.RedisTemplate; 14 | import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; 15 | import org.springframework.data.redis.serializer.StringRedisSerializer; 16 | 17 | import java.net.UnknownHostException; 18 | 19 | @Configuration 20 | public class RedisConfig { 21 | //配置我们自己的redisTemplate 固定模板 22 | 23 | @Bean 24 | @SuppressWarnings("all") //告诉编译器忽略全部的警告,不用在编译完成后出现警告信息 25 | public RedisTemplate redisTemplate(RedisConnectionFactory factory) 26 | throws UnknownHostException { 27 | 28 | //我们为了自己开发方便,一般直接使用类型 29 | RedisTemplate template = new RedisTemplate(); 30 | //连接工厂 31 | template.setConnectionFactory(factory); 32 | 33 | //Json的序列化配置 34 | Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); 35 | ObjectMapper om = new ObjectMapper(); //JackSon对象 36 | om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); 37 | om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); 38 | jackson2JsonRedisSerializer.setObjectMapper(om); 39 | 40 | //String类型的序列化配置 41 | StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); 42 | 43 | 44 | //Key采用String的序列化操作 45 | template.setKeySerializer(stringRedisSerializer); 46 | //Hash的key也采用String的序列化方式 47 | template.setHashKeySerializer(stringRedisSerializer); 48 | //value序列化采用jackson 49 | template.setValueSerializer(jackson2JsonRedisSerializer); 50 | //Hash的value序列化也采用jackson 51 | template.setHashValueSerializer(jackson2JsonRedisSerializer); 52 | 53 | //配置完之后将所有的properties设置进去 54 | template.afterPropertiesSet(); 55 | return template; 56 | } 57 | } -------------------------------------------------------------------------------- /src/main/resources/static/css/common.css: -------------------------------------------------------------------------------- 1 | /* 放置一些公共的样式. 比如导航栏 */ 2 | 3 | /* 清除浏览器默认样式 */ 4 | * { 5 | margin: 0; 6 | padding: 0; 7 | box-sizing: border-box; 8 | } 9 | 10 | /* 给整体页面设置一个高度, 让页面和浏览器窗口一样高 */ 11 | html, body { 12 | /* 设置页面高度和浏览器窗口一样高 */ 13 | height: 100%; 14 | 15 | /* 设置一个背景图片 */ 16 | background-image: url(../img/444.jpg); 17 | background-position: center center; 18 | background-size: cover; 19 | background-repeat: no-repeat; 20 | } 21 | 22 | /* 给导航栏设置样式 */ 23 | .nav { 24 | width: 100%; 25 | height: 50px; 26 | background-color: rgba(51, 51, 51, 0.4); 27 | color: white; 28 | /* 让导航里的内容垂直居中 */ 29 | display: flex; 30 | justify-content: flex-start; 31 | align-items: center; 32 | } 33 | 34 | /* 给 logo 设置样式 */ 35 | .nav img { 36 | /* 给图片设置尺寸 */ 37 | width: 40px; 38 | height: 40px; 39 | /* 把图片变圆 */ 40 | border-radius: 50%; 41 | /* 让图片距离左侧有点空隙 */ 42 | margin-left: 30px; 43 | /* 让图片距离右侧的标题也有点空隙 */ 44 | margin-right: 10px; 45 | } 46 | 47 | .nav .spacer { 48 | width: 70%; 49 | } 50 | 51 | .nav a { 52 | color: #fff; 53 | text-decoration: none; 54 | padding: 0 10px; 55 | } 56 | 57 | /* 版心的样式. 这个内容也是要多个页面引用 */ 58 | .container { 59 | /* 设置宽度 */ 60 | width: 1000px; 61 | /* 设置高度 */ 62 | height: calc(100% - 50px); 63 | /* 设置水平居中 */ 64 | margin: 0 auto; 65 | /* 设置弹性布局 */ 66 | display: flex; 67 | justify-content: space-between; 68 | align-items: center; 69 | } 70 | 71 | .container .left { 72 | height: 100%; 73 | width: 200px; 74 | } 75 | 76 | .container .right { 77 | height: 100%; 78 | /* 让右侧留出 5px 的空隙! */ 79 | width: 795px; 80 | /* 加上背景色, 防止博客内容看不清 */ 81 | background-color: rgba(255, 255, 255, 0.8); 82 | border-radius: 10px; 83 | 84 | /* 如果内容溢出, 就加上滚动条 */ 85 | overflow: auto; 86 | } 87 | 88 | /* 展示用户信息的卡片 */ 89 | .card { 90 | background-color: rgba(255, 255, 255, 0.8); 91 | border-radius: 10px; 92 | /* 设置一下内边距 */ 93 | padding: 30px; 94 | } 95 | 96 | /* 用户头像 */ 97 | .card img { 98 | width: 140px; 99 | height: 140px; 100 | border-radius: 50%; 101 | } 102 | 103 | /* 用户名 */ 104 | .card h3 { 105 | text-align: center; 106 | padding: 10px; 107 | } 108 | 109 | /* github 链接 */ 110 | .card a { 111 | display: block; 112 | text-align: center; 113 | color: #888; 114 | text-decoration: none; 115 | padding: 10px; 116 | } 117 | 118 | .card .counter { 119 | display: flex; 120 | justify-content: space-around; 121 | padding: 5px; 122 | } -------------------------------------------------------------------------------- /src/main/java/com/example/demo/controller/BaseController.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.controller; 2 | 3 | import com.example.demo.exception.*; 4 | import com.example.demo.pojo.JsonResult; 5 | import org.springframework.web.bind.annotation.ExceptionHandler; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | import java.io.FileNotFoundException; 9 | 10 | @RestController 11 | public class BaseController { 12 | 13 | // 用来处理各种异常 14 | @ExceptionHandler({ServiceException.class}) 15 | public JsonResult handlerException(Throwable e){ 16 | 17 | //HashMap map = new HashMap<>(); 18 | JsonResult result = new JsonResult<>(); 19 | 20 | /** 21 | * 编译运行项目的异常都是以 2开头的 22 | * 注册的异常都是以 1开头的 23 | * 用户名登陆的异常都是以 3开头的 24 | * 手机验证码登陆的异常都是以 4开头的 25 | */ 26 | 27 | if(e instanceof FileNotFoundException){ 28 | //map.put("status",2000);//规定返回2000是题目为找到的异常 29 | //map.put("message","输入的id不合法,未找到题目!"); 30 | 31 | result.setState(2001); 32 | result.setMessage("输入的id不合法,未找到题目!"); 33 | 34 | }else if(e instanceof UserNotFoundException){ 35 | //map.put("status",2001); 36 | //map.put("message","输入的id不合法,找不到该用户!"); 37 | 38 | result.setState(2001); 39 | result.setMessage("输入的id不合法,找不到该用户!"); 40 | 41 | }else if(e instanceof InsertException){ 42 | //map.put("status",1002); 43 | //map.put("message","注册时服务器异常,注册失败!"); 44 | 45 | result.setState(1002); 46 | result.setMessage("注册时服务器异常,注册失败!"); 47 | 48 | }else if(e instanceof PasswordNotMatchException){ 49 | //map.put("status",3001); 50 | //map.put("message","密码与用户名不匹配,请重新输入!"); 51 | 52 | result.setState(3001); 53 | result.setMessage("密码与用户名不匹配,请重新输入!"); 54 | 55 | }else if(e instanceof UsernameDuplicatedException){ 56 | //map.put("status",1004); 57 | //map.put("message","用户名已被占用,请重新输入!"); 58 | 59 | result.setState(1004); 60 | result.setMessage("用户名已被占用,请重新输入!"); 61 | 62 | }else if(e instanceof UsernameNotFoundException){ 63 | //map.put("status",3002); 64 | //map.put("message","输入的用户名不存在,请重新输入!"); 65 | 66 | result.setState(3002); 67 | result.setMessage("输入的用户名不存在,请重新输入!"); 68 | 69 | }else if(e instanceof DeleteException){ 70 | 71 | result.setState(6010); 72 | result.setMessage("服务器异常,删除失败!"); 73 | }else if(e instanceof UpdateException){ 74 | result.setMessage("服务器异常,修改失败!"); 75 | } 76 | 77 | //return map; 78 | return result; 79 | 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/main/resources/static/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 登录 8 | 9 | 10 | 11 | 12 | 13 | 24 | 25 | 26 | 63 | 64 | 65 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/controller/CompileController.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.controller; 2 | 3 | import com.example.demo.exception.ProblemNotFountException; 4 | import com.example.demo.mapper.ProblemMapper; 5 | import com.example.demo.pojo.Answer; 6 | import com.example.demo.pojo.JsonRequest; 7 | import com.example.demo.pojo.Problem; 8 | import com.example.demo.pojo.Question; 9 | import com.example.demo.service.TaskService; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.web.bind.annotation.*; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | @RestController 17 | @RequestMapping("/oj") 18 | @CrossOrigin 19 | public class CompileController extends BaseController{ 20 | 21 | @Autowired 22 | public TaskService taskService; 23 | 24 | @Autowired 25 | public ProblemMapper problemMapper; 26 | 27 | @PostMapping(value = "/compile",produces = "application/json") 28 | public Answer getCompile(@RequestBody JsonRequest jsonRequest) throws ProblemNotFountException { 29 | 30 | //0、读取传递的正文和id,json传递 31 | int id = Integer.parseInt(jsonRequest.getId()); 32 | String code = jsonRequest.getCode(); 33 | 34 | //System.out.println(id); 35 | //System.out.println(code); 36 | 37 | //0.5、检查用户提交的代码的安全性,通过对list的黑名单进行查找 38 | if(!checkCodeSafe(code)){ 39 | //System.out.println("用户提交了不安全的代码!"); 40 | //HashMap map = new HashMap<>(); 41 | //map.put("error",3); 42 | //map.put("reason","您提交了不安全的代码,可能危害服务器的安全,编译运行失败!"); 43 | //return map; 44 | 45 | Answer answer = new Answer(); 46 | answer.setError(3); 47 | answer.setReason("您提交了不安全的代码,危害服务器安全,编译失败!"); 48 | 49 | return answer; 50 | 51 | } 52 | 53 | //1、从数据库中查找到题目,拿到测试用例代码 54 | Problem problem = problemMapper.selectOne(id); 55 | 56 | if(problem==null){ 57 | throw new ProblemNotFountException("提交的id非法,找不到题目信息!"); 58 | } 59 | 60 | String testCode = problem.getTestCode(); 61 | 62 | //2、把用户提交的代码与测试用例代码拼接成一个完整的代码 63 | String finalCode = mergeCode(code,testCode); 64 | // System.out.println(finalCode); 65 | 66 | //3、创建一个task,调用编译运行结果 67 | Question question = new Question(); 68 | question.setCode(finalCode); 69 | 70 | Answer answer = taskService.compileAndRun(question); 71 | 72 | //4根据Task运行的结果,包装成一个 HTTP响应 73 | //HashMap map = new HashMap<>(); 74 | //map.put("error",answer.getError()); 75 | //map.put("reason",answer.getReason()); 76 | //map.put("stdout",answer.getStdout()); 77 | 78 | 79 | //return map; 80 | 81 | return answer; 82 | } 83 | 84 | private String mergeCode(String code, String testCode) { 85 | 86 | // 查找code中 的最后一个 } 87 | int pos = code.lastIndexOf('}'); 88 | if(pos==-1){// 如果没有找到的话,那么直接返回null,无法拼接 89 | return null; 90 | } 91 | // code截取之后与testCode进行拼接 92 | return code.substring(0,pos)+testCode+"\n}"; 93 | 94 | } 95 | 96 | // 检查代码的安全性的方法 97 | private boolean checkCodeSafe(String code) { 98 | List black = new ArrayList<>(); 99 | // 黑名单是随时扩充的,防止提交的代码危害服务安全 100 | black.add("Runtime");// 防止提交的代码运行恶意程序 101 | black.add("exec"); 102 | black.add("java.io");// 禁止提交的代码读写文件 103 | black.add("java.net"); // 禁止提交的代码访问网络 104 | 105 | for (String target:black) { 106 | int pos = code.indexOf(target); 107 | if(pos>=0){ 108 | // 找到任意的恶意代码特征,返回false表示不安全 109 | return false; 110 | } 111 | } 112 | return true; 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /src/main/resources/static/updatePassword.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 注册 8 | 9 | 10 | 11 | 12 | 13 | 24 | 25 | 26 | 58 | 59 | 60 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/service/impl/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.service.impl; 2 | 3 | import com.example.demo.exception.InsertException; 4 | import com.example.demo.exception.PasswordNotMatchException; 5 | import com.example.demo.exception.UsernameDuplicatedException; 6 | import com.example.demo.exception.UsernameNotFoundException; 7 | import com.example.demo.mapper.UserMapper; 8 | import com.example.demo.pojo.User; 9 | import com.example.demo.service.UserService; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Service; 12 | import org.springframework.util.DigestUtils; 13 | import org.springframework.util.StringUtils; 14 | 15 | import java.util.Date; 16 | import java.util.UUID; 17 | 18 | /** 19 | * 当前这个类 用于实现 用户登陆、注册等具体业务 20 | */ 21 | 22 | @Service 23 | public class UserServiceImpl implements UserService { 24 | 25 | @Autowired 26 | public UserMapper userMapper; 27 | 28 | /** 29 | * 下面的方法用于注册业务 30 | * @param user 用户的数据对象 31 | */ 32 | @Override 33 | public void register(User user) throws UsernameDuplicatedException, InsertException { 34 | 35 | // 通过user参数获取传递过来的name 36 | String name = user.getUsername(); 37 | 38 | // 调用userMapper 的 selectByName,判断用户名是否已存在 39 | User result = userMapper.selectByName(name); 40 | 41 | // 判断结果集是否为null 42 | 43 | // 如果不为null,那么抛出用户名被占用的异常 44 | if(result!=null){ 45 | throw new UsernameDuplicatedException("用户名已被注册!"); 46 | } 47 | 48 | // 密码加密处理的实现 md5算法的形式 49 | // 乱串 + password + 串 ----md5算法进行加密,整体连续加载三次 50 | // 串 称为—盐值-一个随机的字符串而已 51 | 52 | String oldPassword = user.getPassword(); 53 | 54 | // 获取盐值(随机生成一个盐值(随机字符串)) 55 | String salt = UUID.randomUUID().toString().toUpperCase(); 56 | // 获取加密之后的密码 57 | String md5Password = getMd5Password(oldPassword,salt); 58 | // 补充密码相关信息 59 | user.setSalt(salt); // 盐值一定要记录,登陆的时候进行加密与数据库进行比对 60 | user.setPassword(md5Password); // 这种方式可以忽略用户设置密码的强度 61 | 62 | //如果为null,那么继续注册 63 | Integer rows = userMapper.insert(user); 64 | 65 | //如果正在执行insert的时候服务器宕机了,那么插入失败 66 | if(rows!=1){ 67 | throw new InsertException("在注册过程中产生了未知的异常"); 68 | } 69 | } 70 | 71 | /** 72 | * 用来对密码进行三次加密 73 | * @param password 用户传递的原密码 74 | * @param salt 随机生成的盐值 75 | * @return 返回加密之后的密码 76 | */ 77 | public static String getMd5Password(String password,String salt){ 78 | // springBoot提供了一个加密工具类 DigestUtils 79 | 80 | for(int i = 0;i<3;i++){ 81 | password = DigestUtils.md5DigestAsHex((salt+password+salt).getBytes()).toUpperCase(); 82 | } 83 | 84 | // 返回加密之后的密码 85 | return password; 86 | 87 | } 88 | 89 | /** 90 | * 91 | * @param username 用户名 92 | * @param password 用户密码 93 | * @return 94 | */ 95 | 96 | @Override 97 | public User login(String username, String password) throws UsernameNotFoundException, PasswordNotMatchException { 98 | // 根据用户名称来查询用户的数据是否存在,如果不存在那么抛出异常 99 | User user = userMapper.selectByName(username); 100 | 101 | if(user==null){ 102 | throw new UsernameNotFoundException("该用户名不存在!"); 103 | } 104 | 105 | // 检测用户的密码是否匹配 106 | // 1、先获取到数据库中加密之后的密码 107 | String oldPassword = user.getPassword(); 108 | 109 | //2、和用户直接传递的密码经过三次md5加密之后的结果进行比较 110 | 111 | //2.1 获取盐值 112 | String salt = user.getSalt(); 113 | 114 | //2。2 调用方法,经过加密 115 | String newPassoword = getMd5Password(password,salt); 116 | 117 | //2.3 比较密码 118 | if(!oldPassword.equals(newPassoword)){ 119 | throw new PasswordNotMatchException("用户密码输入错误!"); 120 | } 121 | 122 | // 此时已经比对成功,成功匹配上了 123 | return user; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/service/impl/TaskServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.service.impl; 2 | 3 | import com.example.demo.pojo.Answer; 4 | import com.example.demo.pojo.Question; 5 | import com.example.demo.service.TaskService; 6 | import com.example.demo.util.CommandUtil; 7 | import com.example.demo.util.FileUtil; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.io.File; 11 | import java.util.ArrayList; 12 | import java.util.HashSet; 13 | import java.util.List; 14 | import java.util.UUID; 15 | 16 | // 每次的编译+运行作为一个Task 17 | @Service 18 | public class TaskServiceImpl implements TaskService { 19 | 20 | // 进程与进程之间是存在独立性的,一个进程很难影响到其他进程 21 | //临时文件进行进程间通信 22 | 23 | // 表示所有临时目录所在的目录 24 | private static String WORK_DIR; 25 | 26 | // 约定代码的类名 27 | private static final String CLASS="Solution"; 28 | 29 | //约定要编译的代码文件名 30 | private static String CODE; 31 | 32 | //存放编译错误信息的文件名 33 | private static String COMPILE_ERROR; 34 | 35 | //存放运行错误信息的文件名 36 | private static String STDERR; 37 | 38 | //存在运行输出信息的文件名 39 | private static String STDOUT; 40 | 41 | public TaskServiceImpl() { 42 | WORK_DIR="./tmp/"+UUID.randomUUID().toString()+"/"; 43 | CODE=WORK_DIR+"Solution.java"; 44 | COMPILE_ERROR = WORK_DIR+"compileError.txt"; 45 | STDERR = WORK_DIR+"stderr.txt"; 46 | STDOUT = WORK_DIR+"stdout.txt"; 47 | } 48 | 49 | // 作为核心方法,编译加运行 50 | //参数:要编译运行的java源代码 51 | //返回值:编译运行的结果,编译出错、运行出错、运行正确 52 | 53 | public Answer compileAndRun(Question question){ 54 | 55 | 56 | 57 | Answer answer = new Answer(); 58 | 59 | File workDir = new File(WORK_DIR); 60 | 61 | if(!workDir.exists()){ 62 | // 如果路径不存在的话,创建多级目录 63 | workDir.mkdirs(); 64 | } 65 | 66 | 67 | 68 | //0、将源代码字符串 code 写入到 Solution.java文件, 69 | // 类名和文件名要求要一致,所以约定类名和文件名都叫做Solution 70 | 71 | FileUtil.writeFile(CODE,question.getCode());// 把code写入到Solution.java文件中 72 | 73 | 74 | //1、调用javac进行编译,需要.java文件 75 | // 如果编译出错,将错误信息写入到stderr中,用一个文件保存compileError.txt,直接返回 76 | 77 | // 此处需要先把编译命令给构造出来 78 | 79 | String compileCmd = String.format("javac -encoding utf8 %s -d %s",CODE,WORK_DIR); 80 | // System.out.println("编译命令: "+compileCmd); 81 | 82 | // utf8识别中文字符 -d 后面指定放置编译生成的.class文件的位置 83 | 84 | CommandUtil.run(compileCmd,null,COMPILE_ERROR); 85 | 86 | // 如果编译出错了,那么错误信息就被记录到COMILE_ERROR这个文件中了,如果没有编译出错,那么这个文件为空 87 | 88 | String compileError = FileUtil.readFile(COMPILE_ERROR); 89 | 90 | if(compileError==null || !compileError.equals("")){ 91 | // System.out.println("编译出错!"); 92 | // 编译出错,直接返回answer 93 | answer.setError(1); 94 | answer.setReason(compileError);// 错误信息读取编译错误文件中读取 95 | return answer; 96 | } 97 | // 编译正确继续向下执行逻辑 98 | 99 | 100 | //2、再次创建子进程,调用java命令,执行.class文件,生成 stdout.txt stderr.txt 101 | // 如果运行出错,直接返回 102 | 103 | String runCmd = String.format("java -classpath %s %s ",WORK_DIR,CLASS); // -classpath 指定.class文件的位置 104 | //System.out.println("运行命令: "+runCmd); 105 | CommandUtil.run(runCmd,STDOUT,STDERR); 106 | 107 | // 读取运行时标准错误的文件,如果非空,那么返回answer,如果不为空,那么继续执行后续逻辑 108 | 109 | String runStderr = FileUtil.readFile(STDERR); 110 | 111 | if(runStderr==null || !runStderr.equals("")){ 112 | // System.out.println("运行出错!"); 113 | // 设置运行错误的返回结果 114 | answer.setError(2); 115 | answer.setReason(runStderr); 116 | return answer; 117 | } 118 | 119 | //3、父进程获取到编译执行的结果,打包成Answer对象返回 120 | // 编译运行的结果,就通过从之前读内存的文件中进行获取 121 | 122 | // System.out.println("编译运行正常!"); 123 | answer.setError(0); 124 | answer.setStdout(FileUtil.readFile(STDOUT)); 125 | 126 | return answer; 127 | } 128 | 129 | 130 | } 131 | -------------------------------------------------------------------------------- /src/main/resources/static/phoneLogin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 手机号登陆 8 | 9 | 10 | 11 | 12 | 13 | 24 | 25 | 26 | 53 | 54 | 55 | 56 | 134 | 135 | 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /src/main/ceshi/测试用例.md: -------------------------------------------------------------------------------- 1 | 2 | # 接口测试 3 | 4 | ## 1、/problem 5 | 6 |
7 | 8 | ### (1)id无参数获取到所有的题目列表 9 | 10 |
11 | 12 | GET 13 | 14 | URL:http://localhost:8080/oj/problems 15 | 16 |
17 | 18 | 结果 19 | ```json 20 | [ 21 | Problem(id=3, title=两数之和, level=简单, description=null, template=null, testCode=null), 22 | Problem(id=4, title=两数之和, level=简单, description=null, template=null, testCode=null), 23 | Problem(id=5, title=两数之和, level=简单, description=null, template=null, testCode=null), Problem(id=6, title=两数之和, level=简单, description=null, template=null, testCode=null) 24 | ] 25 | ``` 26 | 27 |
28 | 29 | ### (2)有参数查找指定id的题目,获取题目详情 30 |
31 | 32 | URL:http://localhost:8080/oj/problems?id=3 33 | 34 |
35 | 36 | 结果 37 | 38 |
39 | 40 | ```json 41 | Problem(id=3, title=两数之和, level=简单, description=给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。 42 | 43 | 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。 44 | 45 | 你可以按任意顺序返回答案。 46 | 47 |   48 | 49 | 示例 1: 50 | 51 | 输入:nums = [ 52 | 2, 53 | 7, 54 | 11, 55 | 15 56 | ], target = 9 57 | 输出:[ 58 | 0, 59 | 1 60 | ] 61 | 解释:因为 nums[ 62 | 0 63 | ] + nums[ 64 | 1 65 | ] == 9 ,返回 [ 66 | 0, 67 | 1 68 | ] 。 69 | 示例 2: 70 | 71 | 输入:nums = [ 72 | 3, 73 | 2, 74 | 4 75 | ], target = 6 76 | 输出:[ 77 | 1, 78 | 2 79 | ] 80 | 示例 3: 81 | 82 | 输入:nums = [ 83 | 3, 84 | 3 85 | ], target = 6 86 | 输出:[ 87 | 0, 88 | 1 89 | ] 90 | 91 | 来源:力扣(LeetCode) 92 | 链接:https: //leetcode.cn/problems/two-sum 93 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。, template=class Solution { 94 | public int[] twoSum(int[] nums, int target) {} 95 | }, testCode= public static void main(String[] args) { 96 | Solution solution = new Solution(); 97 | 98 | 99 | //testcase1 100 | //nums = [2,7,11,15], target = 9 101 | //[0,1] 102 | int[] nums1 = { 103 | 2, 104 | 7, 105 | 11, 106 | 15 107 | }; 108 | int target = 9; 109 | int[] result1 = solution.twoSum(nums1,target); 110 | if(result1.length==2 && result1[ 111 | 0 112 | ]==0 && result1[ 113 | 1 114 | ]==1){ 115 | System.out.println("testcase1 OK!"); 116 | }else{ 117 | System.out.println("testcase1 Failed!"); 118 | } 119 | //nums = [3,2,4], target = 6 120 | //[1,2] 121 | int[] nums2 = { 122 | 3, 123 | 2, 124 | 4 125 | }; 126 | target=6; 127 | int[] result2 = solution.twoSum(nums2,target); 128 | if(result2.length==2 && result2[ 129 | 0 130 | ]==1 && result2[ 131 | 1 132 | ]==2){ 133 | System.out.println("testcase2 OK!"); 134 | }else{ 135 | System.out.println("testcase2 Failed!"); 136 | } 137 | //输入:nums = [3,3], target = 6 138 | //输出:[0,1] 139 | int[] nums3 = { 140 | 3, 141 | 3 142 | }; 143 | target=6; 144 | int[] result3 = solution.twoSum(nums3,target); 145 | if(result3.length==2 && result3[ 146 | 0 147 | ]==0 && result3[ 148 | 1 149 | ]==1){ 150 | System.out.println("testcase3 OK!"); 151 | }else{ 152 | System.out.println("testcase3 Failed!"); 153 | } 154 | }) 155 | ``` 156 | 157 | 158 |
159 | 160 | ## /compile 161 | 162 |
163 | 164 | POST 165 | 166 | URL:http://localhost:8080/oj/compile 167 | 168 | 169 |
170 | 171 | body 172 | 173 | ```json 174 | { 175 | "id":3, 176 | "code":"class Solution {public int[] twoSum(int[] nums, int target) {int[] a = {0,1/0};return a;}}" 177 | } 178 | ``` 179 | 180 |
181 | 182 | 运行正确的结果 183 | 184 | ```json 185 | { 186 | "reason": null, 187 | "stdout": "testcase1 OK!\r\ntestcase2 Failed!\r\ntestcase3 OK!\r\n", 188 | "error": 0 189 | } 190 | ``` 191 | 192 |
193 | 194 | 编译出错的结果 195 | ```json 196 | { 197 | "reason": ".\\tmp\\Solution.java:2: ����: �Ҳ�������\r\n Solution solution = new Solution();\r\n ^\r\n ����: �� Solution\r\n λ��: �� olution\r\n.\\tmp\\Solution.java:2: ����: �Ҳ�������\r\n Solution solution = new Solution();\r\n ^\r\n ����: �� Solution\r\n λ��: �� olution\r\n2 ������\r\n", 198 | "stdout": null, 199 | "error": 1 200 | } 201 | ``` 202 | 203 |
204 | 205 | 运行出错的结果 206 | ```json 207 | { 208 | "reason": "Exception in thread \"main\" java.lang.ArithmeticException: / by zero\r\n\tat Solution.twoSum(Solution.java:1)\r\n\tat Solution.main(Solution.java:10)\r\n", 209 | "stdout": null, 210 | "error": 2 211 | } 212 | ``` -------------------------------------------------------------------------------- /src/main/resources/static/missPassword.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 注册 8 | 9 | 10 | 11 | 12 | 13 | 24 | 25 | 26 | 53 | 54 | 55 | 157 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /src/main/resources/static/userUpdate.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 后台-题目信息管理 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 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 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /src/test/java/com/example/demo/DemoApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.example.demo; 2 | 3 | import com.example.demo.exception.InsertException; 4 | import com.example.demo.exception.UsernameDuplicatedException; 5 | import com.example.demo.mapper.ProblemMapper; 6 | import com.example.demo.mapper.UserMapper; 7 | import com.example.demo.pojo.Problem; 8 | import com.example.demo.pojo.User; 9 | import com.example.demo.service.UserService; 10 | import com.example.demo.service.impl.UserServiceImpl; 11 | import org.junit.jupiter.api.Test; 12 | import org.mybatis.spring.annotation.MapperScan; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.boot.test.context.SpringBootTest; 15 | import org.springframework.context.annotation.ComponentScan; 16 | 17 | @SpringBootTest 18 | @MapperScan("com.example.demo.mappers") 19 | class DemoApplicationTests { 20 | 21 | @Autowired 22 | public ProblemMapper problemMapper; 23 | 24 | @Autowired 25 | public UserMapper userMapper; 26 | 27 | @Autowired 28 | public UserService userService; 29 | 30 | @Test 31 | void contextLoads() throws InsertException, UsernameDuplicatedException { 32 | Problem problem = new Problem(); 33 | problem.setId(0); 34 | problem.setTitle("两数之和"); 35 | problem.setLevel("简单"); 36 | problem.setDescription("给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。\n" + 37 | "\n" + 38 | "你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。\n" + 39 | "\n" + 40 | "你可以按任意顺序返回答案。\n" + 41 | "\n" + 42 | " \n" + 43 | "\n" + 44 | "示例 1:\n" + 45 | "\n" + 46 | "输入:nums = [2,7,11,15], target = 9\n" + 47 | "输出:[0,1]\n" + 48 | "解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。\n" + 49 | "示例 2:\n" + 50 | "\n" + 51 | "输入:nums = [3,2,4], target = 6\n" + 52 | "输出:[1,2]\n" + 53 | "示例 3:\n" + 54 | "\n" + 55 | "输入:nums = [3,3], target = 6\n" + 56 | "输出:[0,1]\n" + 57 | "\n" + 58 | "来源:力扣(LeetCode)\n" + 59 | "链接:https://leetcode.cn/problems/two-sum\n" + 60 | "著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。"); 61 | 62 | problem.setTemplate("class Solution {\n" + 63 | " public int[] twoSum(int[] nums, int target) {\n" + 64 | "\n" + 65 | " }\n" + 66 | "}"); 67 | 68 | problem.setTestCode(" public static void main(String[] args) {\n" + 69 | " Solution solution = new Solution();\n" + 70 | "\n" + 71 | "\n" + 72 | " //testcase1\n" + 73 | " //nums = [2,7,11,15], target = 9\n" + 74 | " //[0,1]\n" + 75 | " int[] nums1 = {2,7,11,15};\n" + 76 | " int target = 9;\n" + 77 | " int[] result1 = solution.twoSum(nums1,target);\n" + 78 | " if(result1.length==2 && result1[0]==0 && result1[1]==1){\n" + 79 | " System.out.println(\"testcase1 OK!\");\n" + 80 | " }else{\n" + 81 | " System.out.println(\"testcase1 Failed!\");\n" + 82 | " }\n" + 83 | "\n" + 84 | "\n" + 85 | " //nums = [3,2,4], target = 6\n" + 86 | " //[1,2]\n" + 87 | " int[] nums2 = {3,2,4};\n" + 88 | " target=6;\n" + 89 | " int[] result2 = solution.twoSum(nums2,target);\n" + 90 | " if(result2.length==2 && result2[0]==1 && result2[1]==2){\n" + 91 | " System.out.println(\"testcase2 OK!\");\n" + 92 | " }else{\n" + 93 | " System.out.println(\"testcase2 Failed!\");\n" + 94 | " }\n" + 95 | " \n" + 96 | " //输入:nums = [3,3], target = 6\n" + 97 | " //输出:[0,1]\n" + 98 | " int[] nums3 = {3,3};\n" + 99 | " target=6;\n" + 100 | " int[] result3 = solution.twoSum(nums3,target);\n" + 101 | " if(result3.length==2 && result3[0]==0 && result3[1]==1){\n" + 102 | " System.out.println(\"testcase3 OK!\");\n" + 103 | " }else{\n" + 104 | " System.out.println(\"testcase3 Failed!\");\n" + 105 | " }\n" + 106 | " }"); 107 | 108 | // problemMapper.insert(problem); 109 | // problemMapper.delete(2); 110 | // problemMapper.insert(problem); 111 | 112 | User user = new User(); 113 | user.setUsername("root"); 114 | user.setPassword("123456"); 115 | user.setSalt("ASDDFDF"); 116 | user.setIsAdmin(1); 117 | userService.register(user); 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2007-present the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import java.net.*; 18 | import java.io.*; 19 | import java.nio.channels.*; 20 | import java.util.Properties; 21 | 22 | public class MavenWrapperDownloader { 23 | 24 | private static final String WRAPPER_VERSION = "0.5.6"; 25 | /** 26 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 27 | */ 28 | private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" 29 | + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; 30 | 31 | /** 32 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 33 | * use instead of the default one. 34 | */ 35 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 36 | ".mvn/wrapper/maven-wrapper.properties"; 37 | 38 | /** 39 | * Path where the maven-wrapper.jar will be saved to. 40 | */ 41 | private static final String MAVEN_WRAPPER_JAR_PATH = 42 | ".mvn/wrapper/maven-wrapper.jar"; 43 | 44 | /** 45 | * Name of the property which should be used to override the default download url for the wrapper. 46 | */ 47 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 48 | 49 | public static void main(String args[]) { 50 | System.out.println("- Downloader started"); 51 | File baseDirectory = new File(args[0]); 52 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 53 | 54 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 55 | // wrapperUrl parameter. 56 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 57 | String url = DEFAULT_DOWNLOAD_URL; 58 | if (mavenWrapperPropertyFile.exists()) { 59 | FileInputStream mavenWrapperPropertyFileInputStream = null; 60 | try { 61 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 62 | Properties mavenWrapperProperties = new Properties(); 63 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 64 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 65 | } catch (IOException e) { 66 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 67 | } finally { 68 | try { 69 | if (mavenWrapperPropertyFileInputStream != null) { 70 | mavenWrapperPropertyFileInputStream.close(); 71 | } 72 | } catch (IOException e) { 73 | // Ignore ... 74 | } 75 | } 76 | } 77 | System.out.println("- Downloading from: " + url); 78 | 79 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 80 | if (!outputFile.getParentFile().exists()) { 81 | if (!outputFile.getParentFile().mkdirs()) { 82 | System.out.println( 83 | "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 84 | } 85 | } 86 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 87 | try { 88 | downloadFileFromURL(url, outputFile); 89 | System.out.println("Done"); 90 | System.exit(0); 91 | } catch (Throwable e) { 92 | System.out.println("- Error downloading"); 93 | e.printStackTrace(); 94 | System.exit(1); 95 | } 96 | } 97 | 98 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 99 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { 100 | String username = System.getenv("MVNW_USERNAME"); 101 | char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); 102 | Authenticator.setDefault(new Authenticator() { 103 | @Override 104 | protected PasswordAuthentication getPasswordAuthentication() { 105 | return new PasswordAuthentication(username, password); 106 | } 107 | }); 108 | } 109 | URL website = new URL(urlString); 110 | ReadableByteChannel rbc; 111 | rbc = Channels.newChannel(website.openStream()); 112 | FileOutputStream fos = new FileOutputStream(destination); 113 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 114 | fos.close(); 115 | rbc.close(); 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/controller/UserManageController.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.controller; 2 | 3 | import com.example.demo.exception.InsertException; 4 | import com.example.demo.exception.UsernameDuplicatedException; 5 | import com.example.demo.exception.DeleteException; 6 | import com.example.demo.exception.UpdateException; 7 | import com.example.demo.mapper.UserMapper; 8 | import com.example.demo.pojo.JsonResult; 9 | import com.example.demo.pojo.User; 10 | import com.example.demo.service.UserService; 11 | import com.example.demo.service.impl.UserServiceImpl; 12 | import com.fasterxml.jackson.core.JsonProcessingException; 13 | import com.fasterxml.jackson.databind.ObjectMapper; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.web.bind.annotation.*; 16 | 17 | import javax.servlet.http.HttpServletResponse; 18 | import javax.servlet.http.HttpSession; 19 | import java.io.IOException; 20 | import java.util.List; 21 | 22 | @RestController 23 | @RequestMapping("/usermanage") 24 | public class UserManageController extends BaseController{ 25 | 26 | @Autowired 27 | public UserService userService; 28 | 29 | @Autowired 30 | public UserMapper userMapper; 31 | 32 | @Autowired 33 | public ObjectMapper objectMapper; 34 | 35 | 36 | // 根据id删除用户 37 | // 6010-6019 38 | @RequestMapping("/delete") 39 | public void delete(String id, HttpServletResponse resp) throws DeleteException, IOException { 40 | 41 | int idString =Integer.parseInt(id.trim()); 42 | 43 | int row = userMapper.delete(idString); 44 | 45 | if(row!=1){ 46 | //System.out.println("删除失败!"); 47 | throw new DeleteException("删除失败!"); 48 | } 49 | 50 | // 此时说明删除成功,转发到管理界面,相当于刷新界面 51 | resp.sendRedirect("../userManage.html"); 52 | } 53 | 54 | // 查询全部用户 55 | //6020-6029 56 | @RequestMapping("/selectAll") 57 | public JsonResult> selectAll() throws JsonProcessingException { 58 | JsonResult> result = new JsonResult<>(); 59 | List list = userMapper.selectAll(); 60 | if(list==null){ 61 | result.setState(6020); 62 | result.setMessage("服务器异常,查询题目列表失败!"); 63 | return result; 64 | } 65 | 66 | result.setState(6021); 67 | result.setMessage("查询成功!"); 68 | result.setData(list); 69 | 70 | return result; 71 | } 72 | 73 | 74 | // 6040-6049 75 | @RequestMapping("/search") 76 | public JsonResult>getUser(@RequestParam(required = false) String username){ 77 | 78 | JsonResult> result = new JsonResult<>(); 79 | 80 | if(username==null){ 81 | result.setState(6040); 82 | result.setMessage("参数非法!"); 83 | return result; 84 | } 85 | 86 | List list = userMapper.selectByLikeName("%"+username.trim()+"%"); 87 | if(list==null|| list.isEmpty()){// 说明查询没有结果 88 | result.setState(6041); 89 | result.setMessage("此次查询未查找到相关结果!"); 90 | return result; 91 | } 92 | 93 | // 查询到了相关的结果 94 | result.setState(6042); 95 | result.setMessage("查找结果已显示在当前页面中!"); 96 | result.setData(list); 97 | 98 | //System.out.println(list); 99 | return result; 100 | 101 | } 102 | 103 | // 6050-6059 104 | @RequestMapping("/isload") 105 | public JsonResult load(HttpSession session){ 106 | 107 | JsonResult result = new JsonResult<>(); 108 | 109 | User user = (User)session.getAttribute("user"); 110 | if(user.getIsAdmin()==1){ 111 | // 说明当前用户是管理员用户 112 | result.setState(6050); 113 | result.setMessage("当前用户是管理员用户!"); 114 | return result; 115 | } 116 | 117 | result.setState(6051); 118 | result.setMessage("当前用户只是普通用户!"); 119 | return result; 120 | } 121 | 122 | // 6060-6069 123 | @RequestMapping("/selectById") 124 | public JsonResult selectOne(String id) throws JsonProcessingException { 125 | JsonResult result = new JsonResult<>(); 126 | User user = userMapper.selectById(Integer.parseInt(id)); 127 | result.setMessage("查询成功!"); 128 | result.setData(user); 129 | return result; 130 | } 131 | 132 | // 6070-6079 133 | @RequestMapping("/updateUser") 134 | public JsonResult updateUser(String id, @RequestBody User user) throws UpdateException { 135 | Integer rows = userMapper.updateUser(Integer.parseInt(id),user.getUsername(),user.getEmail()); 136 | 137 | if(rows!=1){ 138 | throw new UpdateException("服务器异常,修改失败!"); 139 | } 140 | 141 | JsonResult result = new JsonResult<>(); 142 | result.setState(6070); 143 | result.setMessage("修改成功"); 144 | return result; 145 | } 146 | 147 | 148 | // 6080-6089 149 | @RequestMapping(value = "/emailUser",produces = "application/json;charset=utf8") 150 | public JsonResult retUser(String email) throws JsonProcessingException { 151 | JsonResult result = new JsonResult<>(); 152 | 153 | User user = userMapper.selectByEmail(email); 154 | if(user==null){ 155 | // 输入的邮箱虽然存在,但是与用户不绑定,就是说这个邮箱不是存在于数据表中的信息 156 | result.setState(6080); 157 | result.setMessage("您的邮箱未注册,响应失败!"); 158 | return result; 159 | } 160 | 161 | result.setState(6081); 162 | result.setMessage("匹配成功,根据该邮箱找到该用户"); 163 | result.setData(user); 164 | return result; 165 | } 166 | 167 | } 168 | -------------------------------------------------------------------------------- /src/main/resources/static/register.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 注册 8 | 9 | 10 | 11 | 12 | 13 | 24 | 25 | 26 | 70 | 71 | 72 | 186 | 187 | 188 | 189 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/controller/ProblemManageController.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.controller; 2 | 3 | import com.example.demo.exception.DeleteException; 4 | import com.example.demo.exception.InsertException; 5 | import com.example.demo.exception.UpdateException; 6 | import com.example.demo.mapper.ProblemMapper; 7 | import com.example.demo.pojo.JsonResult; 8 | import com.example.demo.pojo.Problem; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.web.bind.annotation.*; 11 | 12 | import javax.servlet.http.HttpServletResponse; 13 | import java.io.IOException; 14 | import java.util.List; 15 | 16 | @RequestMapping("/problemmanage") 17 | @RestController 18 | public class ProblemManageController extends BaseController{ 19 | 20 | @Autowired 21 | public ProblemMapper problemMapper; 22 | 23 | //1、处理增加题目的接口 24 | //7000-7009 25 | @RequestMapping("/insert") 26 | public JsonResult add(@RequestBody Problem problem) throws InsertException { 27 | 28 | JsonResult result = new JsonResult<>(); 29 | 30 | if(problem==null){ 31 | result.setMessage("参数非法!"); 32 | return result; 33 | } 34 | 35 | if(problem.getTitle().trim().equals("")||problem.getLevel().trim().equals("") 36 | ||problem.getDescription().trim().equals("") 37 | || problem.getTemplate().trim().equals("") 38 | || problem.getTestCode().trim().equals("") 39 | ){ 40 | result.setMessage("您提交的信息不完整,请补全题目信息在提交!"); 41 | return result; 42 | } 43 | 44 | int row = problemMapper.insert(problem); 45 | if(row!=1){ 46 | throw new InsertException("增加失败!"); 47 | } 48 | 49 | result.setMessage("增加成功!"); 50 | return result; 51 | } 52 | 53 | //2、处理修改题目的接口,按照题目id进行修改 54 | //7010-7019 55 | @RequestMapping("/update") 56 | public JsonResult update(String id, @RequestBody Problem problem) throws UpdateException { 57 | 58 | //System.out.println(problem); 59 | 60 | JsonResult result = new JsonResult<>(); 61 | 62 | if(id==null||id.equals("")||problem==null){ 63 | result.setMessage("参数非法!"); 64 | return result; 65 | } 66 | 67 | if(problem.getTitle().trim().equals("") || 68 | problem.getLevel().trim().equals("")|| 69 | problem.getDescription().trim().equals("")|| 70 | problem.getTemplate().trim().equals("")|| 71 | problem.getTestCode().trim().equals("") 72 | ){ 73 | result.setMessage("填入的题目信息不全,请补全信息后再次提交!"); 74 | return result; 75 | } 76 | 77 | int idString = Integer.parseInt(id); 78 | int rows = problemMapper.updateById( 79 | idString,problem.getTitle().trim(), 80 | problem.getLevel().trim(), problem.getDescription().trim(), 81 | problem.getTemplate().trim(),problem.getTestCode().trim() 82 | ); 83 | 84 | if(rows!=1){ 85 | throw new UpdateException("修改失败"); 86 | } 87 | 88 | result.setMessage("修改成功!"); 89 | return result; 90 | } 91 | 92 | //3、处理删除题目的接口,按照题目id进行删除 93 | //7020-7029 94 | @RequestMapping("/delete") 95 | public JsonResult delete(String id, HttpServletResponse resp) throws DeleteException, IOException { 96 | JsonResult result =new JsonResult<>(); 97 | 98 | if(id==null || id.equals("")){ 99 | result.setMessage("id参数不能为空"); 100 | return result; 101 | } 102 | 103 | int idString = Integer.parseInt(id); 104 | int row = problemMapper.delete(idString); 105 | if(row!=1){ 106 | throw new DeleteException("删除失败"); 107 | } 108 | 109 | result.setMessage("删除成功!"); 110 | 111 | resp.sendRedirect("../problemManage.html"); 112 | 113 | return result; 114 | } 115 | 116 | //4、查找,实现按照题目名字 模糊查询进行查找 117 | //7030-7039 118 | @RequestMapping("/select") 119 | public JsonResult> select(String title){ 120 | 121 | JsonResult> result = new JsonResult<>(); 122 | 123 | if(title==null){ 124 | result.setState(7031); 125 | result.setMessage("参数非法!"); 126 | return result; 127 | } 128 | 129 | List list = problemMapper.selectByLikeTitle(title.trim()); 130 | if(list==null||list.isEmpty()){ 131 | result.setState(7032); 132 | result.setMessage("未查询到相关题目信息!"); 133 | return result; 134 | } 135 | 136 | result.setState(7033); 137 | result.setMessage("查询到了相关信息!"); 138 | result.setData(list); 139 | return result; 140 | } 141 | 142 | @RequestMapping("/selectAll") 143 | //7040-7049 144 | public JsonResult> selectAll(){ 145 | 146 | JsonResult> result = new JsonResult<>(); 147 | 148 | List list = problemMapper.selectAll(); 149 | if(list==null||list.isEmpty()){ 150 | result.setMessage("未查询到相关题目信息!"); 151 | return result; 152 | } 153 | 154 | result.setMessage("查询到了相关信息!"); 155 | result.setData(list); 156 | return result; 157 | } 158 | 159 | @RequestMapping("/selectById") 160 | //7050-7059 161 | public JsonResult selectById(String id){ 162 | //System.out.println(id); 163 | 164 | JsonResult result = new JsonResult<>(); 165 | 166 | if(id==null|| id.equals("")){ 167 | result.setMessage("id参数不合法!"); 168 | return result; 169 | } 170 | 171 | int idString = Integer.parseInt(id); 172 | Problem problem = problemMapper.selectOne(idString); 173 | if(problem==null){ 174 | result.setMessage("未查找到相关题目信息!"); 175 | return result; 176 | } 177 | result.setState(7051); 178 | result.setMessage("已查询到相关信息显示在当前界面上"); 179 | result.setData(problem); 180 | 181 | //System.out.println(problem); 182 | 183 | return result; 184 | } 185 | 186 | } 187 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | com.example 6 | demo 7 | 0.0.1-SNAPSHOT 8 | demo 9 | Demo project for Spring Boot 10 | 11 | 12 | 13 | 14 | 1.8 15 | UTF-8 16 | UTF-8 17 | 2.3.7.RELEASE 18 | 19 | 20 | 21 | 22 | 23 | com.tencentcloudapi 24 | tencentcloud-sdk-java 25 | 26 | 27 | 3.1.572 28 | 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-data-redis-reactive 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-web 37 | 38 | 39 | com.baomidou 40 | mybatis-plus-boot-starter 41 | 3.4.2 42 | 43 | 44 | org.mybatis.spring.boot 45 | mybatis-spring-boot-starter 46 | 2.1.4 47 | 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-devtools 52 | runtime 53 | true 54 | 55 | 56 | mysql 57 | mysql-connector-java 58 | runtime 59 | 60 | 61 | org.projectlombok 62 | lombok 63 | true 64 | 65 | 66 | org.springframework.boot 67 | spring-boot-starter-test 68 | test 69 | 70 | 71 | org.junit.vintage 72 | junit-vintage-engine 73 | 74 | 75 | 76 | 77 | io.projectreactor 78 | reactor-test 79 | test 80 | 81 | 82 | junit 83 | junit 84 | test 85 | 86 | 87 | org.springframework.boot 88 | spring-boot-starter-mail 89 | 90 | 91 | 92 | 93 | 94 | 95 | org.springframework.boot 96 | spring-boot-dependencies 97 | ${spring-boot.version} 98 | pom 99 | import 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | src/main/resources 108 | 109 | **/*.* 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | org.springframework.boot 118 | spring-boot-maven-plugin 119 | 120 | true 121 | 122 | 123 | 124 | 125 | org.apache.maven.plugins 126 | maven-compiler-plugin 127 | 3.8.1 128 | 129 | 1.8 130 | 1.8 131 | UTF-8 132 | 133 | 134 | 135 | org.springframework.boot 136 | spring-boot-maven-plugin 137 | 2.3.7.RELEASE 138 | 139 | com.example.demo.DemoApplication 140 | 141 | 142 | 143 | repackage 144 | 145 | repackage 146 | 147 | 148 | 149 | 150 | 151 | 152 | oj_project 153 | 154 | 155 | 156 | jar 157 | 158 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /src/main/resources/static/problemManage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 后台-用户信息管理 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 28 | 29 | 30 | 31 | 32 |
33 |
34 | 35 | 36 |
37 |
38 |
39 |

题目信息后台管理

40 | 41 |
42 |
43 | 44 | 45 | 46 | 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 |
题目ID标题编辑1编辑2
92 |
93 |
94 |
95 | 96 |
97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 183 | 184 | 185 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/service/impl/SendSmsServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.service.impl; 2 | 3 | import com.example.demo.pojo.TencentCloudUser; 4 | import com.example.demo.service.SendSmsService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.beans.factory.annotation.Qualifier; 7 | import org.springframework.data.redis.core.RedisTemplate; 8 | import org.springframework.stereotype.Service; 9 | import com.tencentcloudapi.common.Credential; 10 | import com.tencentcloudapi.common.exception.TencentCloudSDKException; 11 | 12 | //导入可选配置类 13 | import com.tencentcloudapi.common.profile.ClientProfile; 14 | import com.tencentcloudapi.common.profile.HttpProfile; 15 | 16 | // 导入对应SMS模块的client 17 | import com.tencentcloudapi.sms.v20210111.SmsClient; 18 | 19 | // 导入要请求接口对应的request response类 20 | import com.tencentcloudapi.sms.v20210111.models.SendSmsRequest; 21 | import com.tencentcloudapi.sms.v20210111.models.SendSmsResponse; 22 | @Service 23 | public class SendSmsServiceImpl implements SendSmsService { 24 | 25 | /** 26 | * @param phoneNum 短信发送的手机号 27 | * @param templateCode 使用的短信模板id 28 | * @param code 发送的手机验证码 29 | * @return 返回是否发送成功 30 | */ 31 | 32 | @Override 33 | public boolean send(String phoneNum, String templateCode, String code) { 34 | 35 | try { 36 | /* 必要步骤: 37 | * 实例化一个认证对象,入参需要传入腾讯云账户密钥对secretId,secretKey。 38 | * 这里采用的是从环境变量读取的方式,需要在环境变量中先设置这两个值。 39 | * 你也可以直接在代码中写死密钥对,但是小心不要将代码复制、上传或者分享给他人, 40 | * 以免泄露密钥对危及你的财产安全。 41 | * SecretId、SecretKey 查询: https://console.cloud.tencent.com/cam/capi */ 42 | Credential cred = new Credential(TencentCloudUser.ID, TencentCloudUser.KEY); 43 | 44 | // 实例化一个http选项,可选,没有特殊需求可以跳过 45 | HttpProfile httpProfile = new HttpProfile(); 46 | // 设置代理(无需要直接忽略) 47 | // httpProfile.setProxyHost("真实代理ip"); 48 | // httpProfile.setProxyPort(真实代理端口); 49 | /* SDK默认使用POST方法。 50 | * 如果你一定要使用GET方法,可以在这里设置。GET方法无法处理一些较大的请求 */ 51 | httpProfile.setReqMethod("POST"); 52 | /* SDK有默认的超时时间,非必要请不要进行调整 53 | * 如有需要请在代码中查阅以获取最新的默认值 */ 54 | httpProfile.setConnTimeout(60); 55 | /* 指定接入地域域名,默认就近地域接入域名为 sms.tencentcloudapi.com ,也支持指定地域域名访问,例如广州地域的域名为 sms.ap-guangzhou.tencentcloudapi.com */ 56 | httpProfile.setEndpoint("sms.tencentcloudapi.com"); 57 | 58 | /* 非必要步骤: 59 | * 实例化一个客户端配置对象,可以指定超时时间等配置 */ 60 | ClientProfile clientProfile = new ClientProfile(); 61 | /* SDK默认用TC3-HMAC-SHA256进行签名 62 | * 非必要请不要修改这个字段 */ 63 | clientProfile.setSignMethod("HmacSHA256"); 64 | clientProfile.setHttpProfile(httpProfile); 65 | /* 实例化要请求产品(以sms为例)的client对象 66 | * 第二个参数是地域信息,可以直接填写字符串ap-guangzhou,支持的地域列表参考 https://cloud.tencent.com/document/api/382/52071#.E5.9C.B0.E5.9F.9F.E5.88.97.E8.A1.A8 */ 67 | SmsClient client = new SmsClient(cred, "ap-guangzhou",clientProfile); 68 | /* 实例化一个请求对象,根据调用的接口和实际情况,可以进一步设置请求参数 69 | * 你可以直接查询SDK源码确定接口有哪些属性可以设置 70 | * 属性可能是基本类型,也可能引用了另一个数据结构 71 | * 推荐使用IDE进行开发,可以方便的跳转查阅各个接口和数据结构的文档说明 */ 72 | SendSmsRequest req = new SendSmsRequest(); 73 | 74 | /* 填充请求参数,这里request对象的成员变量即对应接口的入参 75 | * 你可以通过官网接口文档或跳转到request对象的定义处查看请求参数的定义 76 | * 基本类型的设置: 77 | * 帮助链接: 78 | * 短信控制台: https://console.cloud.tencent.com/smsv2 79 | * 腾讯云短信小助手: https://cloud.tencent.com/document/product/382/3773#.E6.8A.80.E6.9C.AF.E4.BA.A4.E6.B5.81 */ 80 | 81 | /* 短信应用ID: 短信SdkAppId在 [短信控制台] 添加应用后生成的实际SdkAppId,示例如1400006666 */ 82 | // 应用 ID 可前往 [短信控制台](https://console.cloud.tencent.com/smsv2/app-manage) 查看 83 | String sdkAppId = "1400723533"; 84 | req.setSmsSdkAppId(sdkAppId); 85 | 86 | /* 短信签名内容: 使用 UTF-8 编码,必须填写已审核通过的签名 */ 87 | // 签名信息可前往 [国内短信](https://console.cloud.tencent.com/smsv2/csms-sign) 或 [国际/港澳台短信](https://console.cloud.tencent.com/smsv2/isms-sign) 的签名管理查看 88 | String signName = "RAIN棋开发公众号"; 89 | req.setSignName(signName); 90 | 91 | /* 模板 ID: 必须填写已审核通过的模板 ID */ 92 | // 模板 ID 可前往 [国内短信](https://console.cloud.tencent.com/smsv2/csms-template) 或 [国际/港澳台短信](https://console.cloud.tencent.com/smsv2/isms-template) 的正文模板管理查看 93 | String templateId = templateCode; 94 | req.setTemplateId(templateId); 95 | 96 | /* 模板参数: 模板参数的个数需要与 TemplateId 对应模板的变量个数保持一致,若无模板参数,则设置为空 */ 97 | String[] templateParamSet = {code}; 98 | req.setTemplateParamSet(templateParamSet); 99 | 100 | /* 下发手机号码,采用 E.164 标准,+[国家或地区码][手机号] 101 | * 示例如:+8613711112222, 其中前面有一个+号 ,86为国家码,13711112222为手机号,最多不要超过200个手机号 */ 102 | String[] phoneNumberSet = {"+86"+phoneNum}; 103 | req.setPhoneNumberSet(phoneNumberSet); 104 | 105 | 106 | 107 | /* 通过 client 对象调用 SendSms 方法发起请求。注意请求方法名与请求对象是对应的 108 | * 返回的 res 是一个 SendSmsResponse 类的实例,与请求对象对应 */ 109 | SendSmsResponse res = client.SendSms(req); 110 | 111 | // 输出json格式的字符串回包 112 | //System.out.println(SendSmsResponse.toJsonString(res)); 113 | return true; 114 | 115 | // 也可以取出单个值,你可以通过官网接口文档或跳转到response对象的定义处查看返回字段的定义 116 | // System.out.println(res.getRequestId()); 117 | 118 | /* 当出现以下错误码时,快速解决方案参考 119 | * [FailedOperation.SignatureIncorrectOrUnapproved](https://cloud.tencent.com/document/product/382/9558#.E7.9F.AD.E4.BF.A1.E5.8F.91.E9.80.81.E6.8F.90.E7.A4.BA.EF.BC.9Afailedoperation.signatureincorrectorunapproved-.E5.A6.82.E4.BD.95.E5.A4.84.E7.90.86.EF.BC.9F) 120 | * [FailedOperation.TemplateIncorrectOrUnapproved](https://cloud.tencent.com/document/product/382/9558#.E7.9F.AD.E4.BF.A1.E5.8F.91.E9.80.81.E6.8F.90.E7.A4.BA.EF.BC.9Afailedoperation.templateincorrectorunapproved-.E5.A6.82.E4.BD.95.E5.A4.84.E7.90.86.EF.BC.9F) 121 | * [UnauthorizedOperation.SmsSdkAppIdVerifyFail](https://cloud.tencent.com/document/product/382/9558#.E7.9F.AD.E4.BF.A1.E5.8F.91.E9.80.81.E6.8F.90.E7.A4.BA.EF.BC.9Aunauthorizedoperation.smssdkappidverifyfail-.E5.A6.82.E4.BD.95.E5.A4.84.E7.90.86.EF.BC.9F) 122 | * [UnsupportedOperation.ContainDomesticAndInternationalPhoneNumber](https://cloud.tencent.com/document/product/382/9558#.E7.9F.AD.E4.BF.A1.E5.8F.91.E9.80.81.E6.8F.90.E7.A4.BA.EF.BC.9Aunsupportedoperation.containdomesticandinternationalphonenumber-.E5.A6.82.E4.BD.95.E5.A4.84.E7.90.86.EF.BC.9F) 123 | * 更多错误,可咨询[腾讯云助手](https://tccc.qcloud.com/web/im/index.html#/chat?webAppId=8fa15978f85cb41f7e2ea36920cb3ae1&title=Sms) 124 | */ 125 | 126 | } catch (TencentCloudSDKException e) { 127 | e.printStackTrace(); 128 | } 129 | return false; 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /src/main/resources/static/problemInsert.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 后台-用户信息管理 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 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 | 204 | 205 | 206 | -------------------------------------------------------------------------------- /src/main/resources/static/problemUpdate.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 后台-题目信息管理 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 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 | 221 | 222 | 223 | -------------------------------------------------------------------------------- /src/main/resources/static/userManage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 后台-用户信息管理 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 28 | 29 | 30 | 31 | 32 |
33 |
34 | 35 | 36 |
37 |
38 |
39 |

用户信息后台管理

40 | 41 |
42 |
43 | 44 | 45 | 46 |
47 |
48 | 49 | 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 |
用户ID用户名密码邮箱编辑1编辑2
84 |
85 |
86 |
87 | 88 |
89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 239 | 240 | 241 | -------------------------------------------------------------------------------- /src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Java 在线 OJ 平台 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 27 | 28 | 29 |
30 |
31 |
32 |
33 |

在线 OJ

34 |

35 | 基于 Java-SpringBoot 搭建的在线 OJ 平台 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 | 68 | 69 | 70 | 71 | 72 |
ID题目难度
73 |
74 |
75 |
76 |
77 |
78 | 79 | 80 |
81 |
82 |
83 |
84 | 92 |

93 | © 2022 RAIN 7 94 |

95 |
96 |
97 |
98 |
99 | 100 | 101 | 102 | 103 | 104 | 199 | 200 | 201 | -------------------------------------------------------------------------------- /src/main/resources/static/problemDetail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | OJ 题目详情 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 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 | 126 |

127 | @ by RAIN 7 128 |

129 |
130 |
131 |
132 |
133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 254 | 255 | 256 | 257 | 258 | 259 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package com.example.demo.controller; 2 | 3 | import com.example.demo.exception.*; 4 | import com.example.demo.mapper.UserMapper; 5 | import com.example.demo.pojo.User; 6 | import com.example.demo.service.SendSmsService; 7 | import com.example.demo.service.UserService; 8 | import com.example.demo.pojo.JsonResult; 9 | import com.example.demo.service.impl.UserServiceImpl; 10 | import org.apache.tomcat.util.security.MD5Encoder; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.beans.factory.annotation.Value; 13 | import org.springframework.data.redis.core.RedisTemplate; 14 | import org.springframework.mail.MailMessage; 15 | import org.springframework.mail.SimpleMailMessage; 16 | import org.springframework.mail.javamail.JavaMailSender; 17 | import org.springframework.util.StringUtils; 18 | import org.springframework.web.bind.annotation.*; 19 | 20 | import javax.servlet.http.HttpServletRequest; 21 | import javax.servlet.http.HttpSession; 22 | import java.util.HashMap; 23 | import java.util.Random; 24 | import java.util.concurrent.TimeUnit; 25 | 26 | /** 27 | * 这个类中的方法专门处理 用户的各种状态 28 | * 登陆、注册、注销 29 | */ 30 | 31 | @RestController 32 | @RequestMapping("/state") 33 | public class UserController extends BaseController{ 34 | @Autowired 35 | public UserService userService; 36 | 37 | @Autowired 38 | public UserMapper userMapper; 39 | 40 | @Autowired 41 | public SendSmsService sendSmsService; 42 | 43 | @Autowired 44 | public RedisTemplate redisTemplate; 45 | 46 | public Random random = new Random(); 47 | 48 | @Autowired 49 | public JavaMailSender javaMailSender; 50 | 51 | @Value("${spring.mail.from}") 52 | public String mailFrom; 53 | 54 | /** 55 | * 56 | * 请求的路径 users/reg 57 | * 这是接收注册的请求 User user 58 | * 响应结果 JsonResult 59 | */ 60 | 61 | /** 62 | * 注册的逻辑 63 | * (1)如果用户名或密码或确认密码为空,那么返回信息提示:用户名或密码不能为空! 64 | * (2)此时都不为空,如果确认密码或密码不一致,那么返回信息提示:两次密码不一致,请重新输入! 65 | * (3)此时都不为空,且两次输入密码一致,查询数据库,如果输入的用户名已经存在了,那么抛出用户名已被占用异常,异常返回信息提示:用户名已被占用! 66 | * (4)此时都没有问题,但是在执行数据库插入数据的过程中,服务器断开了连接或者宕机了,此时抛出服务器中断异常,异常返回信息提示:服务器异常,注册失败! 67 | * (5)排除前面的所有情况,此时注册成功,返回信息提示:注册成功! 68 | * 69 | */ 70 | static class RegUser{ 71 | public String username; 72 | public String password; 73 | public String email; 74 | public String emailCode; 75 | } 76 | 77 | @RequestMapping(value = "/reg",produces = "application/json;charset=utf8") 78 | public JsonResult register(@RequestBody RegUser regUser) throws InsertException, UsernameDuplicatedException { 79 | 80 | 81 | // 如果邮箱地址存在,那么验证码必然存在,前端决定的 82 | // 如果邮箱地址不存在,那么验证码必然不存在 83 | JsonResult result = new JsonResult<>(); 84 | User user = new User(); 85 | 86 | // 87 | if(!regUser.email.equals("")){ 88 | // 说明邮箱地址存在,比对验证码 89 | if(redisTemplate.opsForValue().get(regUser.email)==null){ 90 | // 如果reids中查不到的话,那么说明这个邮箱地址写了,但是没发验证码,没有保存在redis中 91 | result.setMessage("注册失败,请先发送验证码,再点击注册"); 92 | return result; 93 | } 94 | 95 | String redisCode = redisTemplate.opsForValue().get(regUser.email).toString(); 96 | if(!redisCode.equals(regUser.emailCode)){ // 比对失败 97 | result.setMessage("验证码输入错误,请重新输入!"); 98 | return result; 99 | }else{ 100 | user.setEmail(regUser.email); 101 | } 102 | } 103 | 104 | user.setUsername(regUser.username); 105 | user.setPassword(regUser.password); 106 | 107 | userService.register(user); 108 | 109 | 110 | result.setState(1003); 111 | result.setMessage("注册成功!"); 112 | return result; 113 | } 114 | 115 | /** 116 | * 用户名密码登陆的逻辑 117 | * (1) 对传入的参数进行非空校验,如果有空的,那么返回信息提示:用户名或密码不能为空 118 | * (2) 如果都不为空,根据用户名进行查找,如果不存在,那么配出用户名不存在异常,异常返回信息提示:您输入的用户名不存在,请重新输入 119 | * (3) 如果用户名存在,那么将用户名和输入的密码(加盐值)进行匹配,如果匹配不上,那么抛出匹配错误异常,异常返回信息提示:输入密码错误,请重新输入 120 | * (4) 如果密码匹配成功,那么返回登陆成功的结果信息,前端跳转到主页 121 | */ 122 | @RequestMapping(value = "/login",produces = "application/json;charset=utf8") 123 | public JsonResult loginByUser(String username, String password, HttpServletRequest request) throws PasswordNotMatchException, UsernameNotFoundException { 124 | 125 | //HashMap map = new HashMap<>(); 126 | JsonResult result = new JsonResult<>(); 127 | 128 | // 对参数进行非空校验 129 | if(username==null ||username.equals("") || password==null || password.equals("") ){ 130 | //map.put("status",3003); 131 | //map.put("message","用户名或密码不能为空!"); 132 | //return map; 133 | 134 | result.setState(3003); 135 | result.setMessage("用户名或密码不能为空!"); 136 | return result; 137 | } 138 | 139 | // 如果都不为空,那么执行登陆业务 140 | User user = userService.login(username,password); 141 | 142 | //如果登陆成功的话,返回登录成功的结果 143 | //map.put("status",3004); 144 | //map.put("message","登陆成功"); 145 | 146 | result.setState(3004); 147 | result.setMessage("登陆成功!"); 148 | 149 | // 设置全局session 150 | HttpSession session =request.getSession(true); 151 | session.setAttribute("user",user); 152 | 153 | //System.out.println("user:"+session.getAttribute("user")); 154 | 155 | //return map; 156 | 157 | return result; 158 | } 159 | 160 | /** 161 | * 发送手机验证码的逻辑 162 | * (0) 先对手机号参数进行非法验证,非空、手机号位数11 163 | * (1) 连接redis,查找手机验证码是否仍然存在未过期,如果未过期的话,那么不能发送,返回信息:验证码还未过期,再次发送失败 164 | * (2) 查询数据库如果不存在验证码的话,那么生成6位随机数,发送验证码, 165 | * 如果发送失败,那么返回结果提示 验证码发送失败,为什么会发送失败?腾讯云每天对发送短信条数频率是会限制的,发送短信是要钱的,个人云账户没钱了等情况都会发生发送失败! 166 | * 如果发送成功,那么保存到redis中,设置过期时间为2分钟,返回信息提示:验证码发送成功,2分钟内有效,请及时填写 167 | * 168 | */ 169 | @RequestMapping(value = "/phoneSend",produces = "application/json;charset=utf8") 170 | public JsonResult sendCode(@RequestParam(value = "phone") String phone) { 171 | 172 | 173 | // System.out.println(phone); 174 | //调用发送的方法即可 175 | 176 | //HashMap map =new HashMap<>(); 177 | JsonResult result = new JsonResult<>(); 178 | 179 | //0、对参数进行校验 180 | if(phone==null|| phone.equals("")){ 181 | //map.put("status",4000); 182 | //map.put("message","输入的手机号不能为空,请重新输入!"); 183 | //return map; 184 | 185 | result.setState(4000); 186 | result.setMessage("输入的手机号不能为空,请重新输入!"); 187 | return result; 188 | } 189 | 190 | if(phone.length()!=11){ 191 | //map.put("status",4001); 192 | //map.put("message","输入的手机号格式非法,请重新输入!"); 193 | //return map; 194 | 195 | result.setState(4001); 196 | result.setMessage("输入的手机号格式非法,请重新输入!"); 197 | return result; 198 | } 199 | 200 | //1、连接Redis,查找手机验证码是否存在 201 | String code = (String)redisTemplate.opsForValue().get(phone); 202 | 203 | //System.out.println("验证码:"+code); 204 | 205 | //==================================================== 206 | //1、1如果存在的话,说明在5分钟内已经发送过验证码了,不能再发了 207 | if (!StringUtils.isEmpty(code)) { 208 | //System.out.println("已存在,还没有过期,不能再次发送"); 209 | //map.put("status",4002); 210 | //map.put("message","验证码还未过期,再次发送失败!"); 211 | //return map; 212 | 213 | result.setState(4002); 214 | result.setMessage("验证码还未过期,再次发送失败!"); 215 | return result; 216 | } 217 | //===================================================== 218 | 219 | //1。2 如果不存在的话,那么redis创建键值对生成验证码并存储,设置过期时间 220 | 221 | // 生成6位随机验证码 222 | String newCode = getNewCode(); 223 | 224 | // 将6位随机验证码对手机号进行发送 225 | boolean idSend = sendSmsService.send(phone,"1511343",newCode); 226 | 227 | //===================================================== 228 | 229 | // 因为有短信轰炸的情况,短信服务对每次发送限制次数,所以有发送不成功的情况,要考虑 230 | 231 | if(idSend){//如果发送成功将验证码存储到redis中 232 | redisTemplate.opsForValue().set(phone, newCode, 2, TimeUnit.MINUTES); 233 | //System.out.println("发送成功!"); 234 | //map.put("status",4003); 235 | //map.put("message","验证码发送成功,2分钟内有效,请及时填写!"); 236 | 237 | result.setState(4003); 238 | result.setMessage("验证码发送成功,2分钟内有效,请及时填写!"); 239 | 240 | }else{ 241 | //System.out.println("发送失败!"); 242 | //map.put("status",4004); 243 | //map.put("message","验证码发送失败!"); 244 | 245 | result.setState(4004); 246 | result.setMessage("验证码发送失败!"); 247 | 248 | } 249 | return result; 250 | } 251 | 252 | /** 253 | * 手机验证码登陆的逻辑 254 | * (1)参数非空校验,phone 和code有空的,那么返回结果,提示信息:手机号或验证码不能为空 255 | * (2)如果不为空,那么根据手机号 查询redis数据库中的 验证码 256 | * (3)如果验证码不存在或者过期,那么返回结果提示信息,手机号输入错误或者超出输入有效时间,登陆失败! 257 | * (4)如果验证码存在,则进行比对,如果比对成功,那么返回登陆成功,前端跳转主页!如果失败,那么返回输入输入验证码错误,登陆失败! 258 | */ 259 | 260 | @RequestMapping("/phoneLogin") 261 | public JsonResult phoneLog(String phone,String code,HttpSession session){ 262 | //HashMap map =new HashMap<>(); 263 | 264 | JsonResult result = new JsonResult<>(); 265 | 266 | //0、对参数进行校验 267 | if(phone==null|| phone.trim().equals("")||code==null || code.trim().equals("")){ 268 | //map.put("status",4005); 269 | //map.put("message","输入的手机号 或 验证码不能为空,请重新输入!"); 270 | //return map; 271 | 272 | result.setState(4005); 273 | result.setMessage("输入的手机号 或 验证码不能为空,请重新输入!"); 274 | return result; 275 | } 276 | 277 | if(phone.trim().length()!=11 || code.trim().length()!=6){ 278 | //map.put("status",4006); 279 | //map.put("message","输入的手机号格式非法 或者 验证码格式错误,请重新输入!"); 280 | //return map; 281 | 282 | result.setState(4006); 283 | result.setMessage("输入的手机号格式非法 或者 验证码格式错误,请重新输入!"); 284 | return result; 285 | } 286 | 287 | //1、根据输入的手机号,在redis中查询验证码 288 | String phoneCode = (String)redisTemplate.opsForValue().get(phone.trim()); 289 | 290 | if(phoneCode==null){// 手机号不存在或者 验证码已经过期了 291 | //map.put("status",4007); 292 | //map.put("message","手机号输入错误 或者 验证码输入超出有效时间,登陆失败!"); 293 | //return map; 294 | 295 | result.setState(4007); 296 | result.setMessage("手机号输入错误 或者 验证码输入超过有效时间,登陆失败!"); 297 | return result; 298 | } 299 | 300 | //2、如果验证码存在的话,进行比对 301 | if(!code.equals(phoneCode.trim())){// 比对失败 302 | //map.put("status",4008); 303 | //map.put("message","验证码输入错误,请重新输入!"); 304 | //return map; 305 | 306 | result.setState(4008); 307 | result.setMessage("验证码输入错误,请重新输入!"); 308 | return result; 309 | } 310 | 311 | //3、如果验证码比对成功,设置全局session,返回登陆成功 312 | session.setAttribute("phone",phone.trim()); 313 | 314 | //map.put("status",4009); 315 | //map.put("message","手机号登陆成功!"); 316 | //return map; 317 | 318 | result.setState(4009); 319 | result.setMessage("手机号登陆成功!"); 320 | return result; 321 | } 322 | 323 | @RequestMapping("/logout") 324 | public JsonResult logout(HttpSession session){ 325 | //HashMap map = new HashMap<>(); 326 | JsonResult result = new JsonResult<>(); 327 | 328 | if(session==null || (session.getAttribute("user")==null && session.getAttribute("phone")==null)){ 329 | // 返回注销失败 330 | result.setState(5001); 331 | result.setMessage("注销失败!"); 332 | return result; 333 | } 334 | 335 | if(session.getAttribute("user")!=null){ 336 | session.removeAttribute("user"); 337 | } 338 | if(session.getAttribute("phone")!=null){ 339 | session.removeAttribute("phone"); 340 | } 341 | 342 | // 此时注销成功 343 | //return map; 344 | 345 | result.setState(5002); 346 | result.setMessage("注销成功!"); 347 | return result; 348 | } 349 | 350 | 351 | @RequestMapping("/emailSend") 352 | public JsonResult sendEmail(String To){ 353 | // 对邮件的格式进行校验(前端解决) 354 | 355 | JsonResult result = new JsonResult<>(); 356 | 357 | if(!isEmail(To)){ 358 | result.setState(5003); 359 | result.setMessage("输入的邮箱格式非法,请重新输入!"); 360 | return result; 361 | } 362 | 363 | if(!StringUtils.isEmpty(redisTemplate.opsForValue().get(To))){ 364 | // 如果在redis中查到已经发送过还没有过期,那么就先不发 365 | result.setState(5004); 366 | result.setMessage("验证码未过期,无法发送,请及时查看"); 367 | return result; 368 | } 369 | 370 | SimpleMailMessage message = new SimpleMailMessage(); 371 | String newCode = getNewCode(); 372 | message.setSubject("注册"); 373 | message.setText("您的注册验证码是:"+newCode+", 有效期2分钟,请及时填写"); 374 | message.setFrom(mailFrom); 375 | message.setTo(To); 376 | 377 | javaMailSender.send(message); 378 | 379 | 380 | //==============将验证码保存redis中设置 过期时间 120s============== 381 | redisTemplate.opsForValue().set(To,newCode,2,TimeUnit.MINUTES); 382 | 383 | 384 | result.setState(5005); 385 | result.setMessage("验证码发送成功,2分钟内有效,请及时填写!"); 386 | return result; 387 | } 388 | 389 | static class RequireEmail{ 390 | public String email; 391 | public String code; 392 | } 393 | 394 | @RequestMapping("/emailRequire") 395 | public JsonResult requireEmail(@RequestBody RequireEmail requireEmail){ 396 | // 不用判断参数合法性 397 | JsonResult result =new JsonResult<>(); 398 | 399 | String email = requireEmail.email; 400 | String code = requireEmail.code; 401 | 402 | //查询redis中是否存在email 403 | String emailCode = (String)redisTemplate.opsForValue().get(email); 404 | 405 | if(emailCode==null){ 406 | // 验证码已经过期了 407 | result.setState(5006); 408 | result.setMessage("邮箱输入错误 或者 验证码已过期!"); 409 | return result; 410 | } 411 | 412 | // 查询到了的话,那么将查询到的验证码与 输入的验证码进行比对 413 | 414 | if(!code.equals(emailCode)){ 415 | //匹配失败 416 | result.setState(5007); 417 | result.setMessage("输入的验证码错误,请重新输入!"); 418 | return result; 419 | } 420 | 421 | // 匹配成功,跳转到重置密码的界面 422 | result.setState(5008); 423 | result.setMessage("验证成功!"); 424 | return result; 425 | 426 | } 427 | 428 | 429 | static class UpdateUser{ 430 | public String username; 431 | public String password; 432 | } 433 | 434 | @RequestMapping("/updatePassword") 435 | public JsonResult updatePassword(@RequestBody UpdateUser updateUser) throws UpdateException { 436 | // 输入密码的逻辑 437 | String username = updateUser.username; 438 | String password = updateUser.password; 439 | 440 | //先获取原用户的盐值,将密码进行md5加密, 441 | User user = userMapper.selectByName(username); 442 | String salt = user.getSalt(); 443 | 444 | // 这是用户修改过的代码经过加密之后的密码,替换即可 445 | String finalPassword = UserServiceImpl.getMd5Password(password,salt); 446 | 447 | //最后将原密码修改为md5加密之后的密码 448 | int rows = userMapper.updatePassword(username,finalPassword); 449 | 450 | if(rows!=1){ 451 | throw new UpdateException("服务器异常,插入失败!"); 452 | } 453 | 454 | JsonResult result = new JsonResult<>(); 455 | result.setMessage("修改成功!"); 456 | return result; 457 | 458 | 459 | } 460 | 461 | /** 462 | * 生成6为随机验证码的方法 463 | * @return 464 | */ 465 | private String getNewCode(){ 466 | String newCode = ""; 467 | for (int i = 0; i < 6; i++) { 468 | newCode += random.nextInt(10); 469 | } 470 | return newCode; 471 | } 472 | 473 | /** 474 | * 校验邮箱格式的代码 475 | * @param str 476 | * @return 477 | */ 478 | public static boolean isEmail(String str) { 479 | // TODO Auto-generated method stub 480 | 481 | String tegex="[a-zA-Z0-9_]+@\\w+(\\.com|\\.cn){1}"; 482 | boolean flag=str.matches(tegex); 483 | return flag; 484 | } 485 | 486 | 487 | 488 | } -------------------------------------------------------------------------------- /src/main/resources/static/css/_variables.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Created by Kroplet (https://www.kroplet.com) 3 | * The easiest way to create Bootstrap 4 themes. 4 | */ 5 | 6 | // 7 | //Colors 8 | // 9 | 10 | // Base Colors 11 | 12 | $white: #ffffff; 13 | $gray-100: #fcfdfd; 14 | $gray-200: #e5eaed; 15 | $gray-300: #ced7dd; 16 | $gray-400: #b7c4cd; 17 | $gray-500: #9fb1be; 18 | $gray-600: #889fae; 19 | $gray-700: #668295; 20 | $gray-800: #566e7e; 21 | $gray-900: #465966; 22 | $black: #36454f; 23 | $blue: #007bff; 24 | $indigo: #6610f2; 25 | $purple: #6f42c1; 26 | $pink: #e83e8c; 27 | $red: #ff3333; 28 | $orange: #fd7e14; 29 | $yellow: #ffc107; 30 | $green: #28a745; 31 | $teal: #20c997; 32 | $cyan: #27cde4; 33 | 34 | 35 | // Theme Colors 36 | 37 | $primary: $pink; 38 | $secondary: $orange; 39 | $success: $green; 40 | $info: $cyan; 41 | $warning: $yellow; 42 | $danger: $red; 43 | $light: $gray-100; 44 | $dark: $gray-800; 45 | 46 | 47 | $theme-colors: (); 48 | $theme-colors: map-merge(( 49 | "primary": $primary, 50 | "secondary": $secondary, 51 | "success": $success, 52 | "info": $info, 53 | "warning": $warning, 54 | "danger": $danger, 55 | "light": $light, 56 | "dark": $dark, 57 | ), $theme-colors); 58 | $theme-color-interval: 8%; 59 | 60 | 61 | // 62 | //Global 63 | // 64 | 65 | $enable-caret: true; 66 | $enable-rounded: true; 67 | $enable-shadows: false; 68 | $enable-gradients: false; 69 | $enable-transitions: true; 70 | $enable-hover-media-query: false; 71 | $enable-grid-classes: true; 72 | $enable-print-styles: true; 73 | 74 | 75 | // 76 | //Spacing 77 | // 78 | 79 | $spacer: 1rem; 80 | $spacers: (0: 0, 1: ($spacer * .25), 2: ($spacer * .5), 3: $spacer, 4: ($spacer * 1.5), 5: ($spacer * 3)); 81 | $sizes: (25: 25%, 50: 50%, 75: 75%, 100: 100%); 82 | 83 | 84 | // 85 | //Body 86 | // 87 | 88 | $body-bg: $black; 89 | $body-color: $gray-100; 90 | 91 | 92 | // 93 | //Links 94 | // 95 | 96 | $link-color: theme-color("primary"); 97 | $link-decoration: none; 98 | $link-hover-color: darken($link-color, 15%); 99 | $link-hover-decoration: underline; 100 | 101 | 102 | // 103 | //Paragraphs 104 | // 105 | 106 | $paragraph-margin-bottom: 1rem; 107 | 108 | 109 | // 110 | //GridBreakpoints 111 | // 112 | 113 | $grid-breakpoints: (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px); 114 | 115 | 116 | // 117 | //GridContainers 118 | // 119 | 120 | $container-max-widths: (sm: 540px, md: 720px, lg: 960px, xl: 1140px); 121 | 122 | 123 | // 124 | //GridColumns 125 | // 126 | 127 | $grid-columns: 12; 128 | $grid-gutter-width: 30px; 129 | 130 | 131 | // 132 | //Components 133 | // 134 | 135 | $line-height-lg: 1.5; 136 | $line-height-sm: 1.5; 137 | $border-width: 1px; 138 | $border-color: $gray-200; 139 | $border-radius: .25rem; 140 | $border-radius-lg: .3rem; 141 | $border-radius-sm: .2rem; 142 | $component-active-color: $white; 143 | $component-active-bg: theme-color("primary"); 144 | $caret-width: .3em; 145 | $transition-base: all .2s ease-in-out; 146 | $transition-fade: opacity .15s linear; 147 | $transition-collapse: height .35s ease; 148 | 149 | 150 | // 151 | //Fonts 152 | // 153 | 154 | $font-family-sans-serif: Barlow, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 155 | $font-family-monospace: "SFMono-Regular", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 156 | $font-family-base: $font-family-sans-serif; 157 | $font-size-base: 1rem; 158 | $font-size-lg: ($font-size-base * 1.25); 159 | $font-size-sm: ($font-size-base * .875); 160 | $font-weight-light: 300; 161 | $font-weight-normal: 400; 162 | $font-weight-bold: 600; 163 | $font-weight-base: $font-weight-normal; 164 | $line-height-base: 1.5; 165 | $h1-font-size: $font-size-base * 2.5; 166 | $h2-font-size: $font-size-base * 2; 167 | $h3-font-size: $font-size-base * 1.75; 168 | $h4-font-size: $font-size-base * 1.5; 169 | $h5-font-size: $font-size-base * 1.25; 170 | $h6-font-size: $font-size-base; 171 | $headings-margin-bottom: ($spacer / 2); 172 | $headings-font-family: inherit; 173 | $headings-font-weight: $font-weight-bold; 174 | $headings-line-height: 1.2; 175 | $headings-color: $white; 176 | $display1-size: 6rem; 177 | $display2-size: 5.5rem; 178 | $display3-size: 4.5rem; 179 | $display4-size: 3.5rem; 180 | $display1-weight: 300; 181 | $display2-weight: 300; 182 | $display3-weight: 300; 183 | $display4-weight: 300; 184 | $display-line-height: $headings-line-height; 185 | $lead-font-size: ($font-size-base * 1.25); 186 | $lead-font-weight: 300; 187 | $small-font-size: 80%; 188 | $text-muted: $gray-300; 189 | $blockquote-small-color: $gray-600; 190 | $blockquote-font-size: ($font-size-base * 1.25); 191 | $hr-border-color: rgba($white,.2); 192 | $hr-border-width: $border-width; 193 | $mark-padding: .2em; 194 | $dt-font-weight: $font-weight-bold; 195 | $kbd-box-shadow: inset 0 -.1rem 0 rgba($black,.25); 196 | $nested-kbd-font-weight: $font-weight-bold; 197 | $list-inline-padding: 5px; 198 | $mark-bg: #fcf8e3; 199 | 200 | 201 | // 202 | //Tables 203 | // 204 | 205 | $table-cell-padding: .75rem; 206 | $table-cell-padding-sm: .3rem; 207 | $table-bg: transparent; 208 | $table-accent-bg: rgba($white,.05); 209 | $table-hover-bg: rgba($white,.075); 210 | $table-active-bg: $table-hover-bg; 211 | $table-border-width: $border-width; 212 | $table-border-color: $gray-600; 213 | $table-head-bg: $gray-200; 214 | $table-head-color: $gray-700; 215 | $table-dark-bg: $gray-900; 216 | $table-dark-accent-bg: rgba($black, .05); 217 | $table-dark-hover-bg: rgba($black, .075); 218 | $table-dark-border-color: lighten($gray-900, 7.5%); 219 | $table-dark-color: $white; 220 | 221 | 222 | // 223 | //Buttons 224 | // 225 | 226 | $input-btn-padding-y: .375rem; 227 | $input-btn-padding-x: 1rem; 228 | $input-btn-line-height: $line-height-base; 229 | $input-btn-focus-width: .2rem; 230 | $input-btn-focus-color: rgba(theme-color("primary"), .25); 231 | $input-btn-focus-box-shadow: 0 0 0 $input-btn-focus-width $input-btn-focus-color; 232 | $input-btn-padding-y-sm: .25rem; 233 | $input-btn-padding-x-sm: .5rem; 234 | $input-btn-line-height-sm: $line-height-sm; 235 | $input-btn-padding-y-lg: .5rem; 236 | $input-btn-padding-x-lg: 1rem; 237 | $input-btn-line-height-lg: $line-height-lg; 238 | $btn-font-weight: $font-weight-bold; 239 | $btn-box-shadow: inset 0 1px 0 rgba($white,.15), 0 1px 1px rgba($black,.075); 240 | $btn-active-box-shadow: inset 0 3px 5px rgba($black,.125); 241 | $btn-link-disabled-color: $gray-600; 242 | $btn-block-spacing-y: .5rem; 243 | $btn-border-radius: $border-radius; 244 | $btn-border-radius-lg: $border-radius-lg; 245 | $btn-border-radius-sm: $border-radius-sm; 246 | $btn-transition: background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out; 247 | 248 | 249 | // 250 | //Forms 251 | // 252 | 253 | $input-bg: $gray-900; 254 | $input-disabled-bg: $gray-900; 255 | $input-color: $gray-100; 256 | $input-border-color: $gray-900; 257 | $input-btn-border-width: $border-width; 258 | $input-box-shadow: inset 0 1px 1px rgba($black,.075); 259 | $input-border-radius: $border-radius; 260 | $input-border-radius-lg: $border-radius-lg; 261 | $input-border-radius-sm: $border-radius-sm; 262 | $input-focus-bg: $gray-800; 263 | $input-focus-border-color: lighten(theme-color("primary"), 75%); 264 | $input-focus-color: $input-color; 265 | $input-placeholder-color: $gray-300; 266 | $input-height-border: $input-btn-border-width * 2; 267 | $input-height-inner: ($font-size-base * $input-btn-line-height) + ($input-btn-padding-y * 2); 268 | $input-height: calc(#{$input-height-inner} + #{$input-height-border}); 269 | $input-height-inner-sm: ($font-size-sm * $input-btn-line-height-sm) + ($input-btn-padding-y-sm * 2); 270 | $input-height-sm: calc(#{$input-height-inner-sm} + #{$input-height-border}); 271 | $input-height-inner-lg: ($font-size-lg * $input-btn-line-height-lg) + ($input-btn-padding-y-lg * 2); 272 | $input-height-lg: calc(#{$input-height-inner-lg} + #{$input-height-border}); 273 | $input-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; 274 | $form-text-margin-top: .25rem; 275 | $form-check-margin-bottom: .5rem; 276 | $form-check-input-gutter: 1.25rem; 277 | $form-check-input-margin-y: .25rem; 278 | $form-check-input-margin-x: .25rem; 279 | $form-check-inline-margin-x: .75rem; 280 | $form-group-margin-bottom: 1rem; 281 | $input-group-addon-color: $input-color; 282 | $input-group-addon-bg: $gray-700; 283 | $input-group-addon-border-color: $gray-700; 284 | $form-feedback-valid-color: theme-color("success"); 285 | $form-feedback-invalid-color: theme-color("danger"); 286 | 287 | 288 | // 289 | //CustomForms 290 | // 291 | 292 | $custom-control-gutter: 1.5rem; 293 | $custom-control-spacer-y: .25rem; 294 | $custom-control-spacer-x: 1rem; 295 | $custom-control-indicator-size: 1rem; 296 | $custom-control-indicator-bg: #ddd; 297 | $custom-control-indicator-bg-size: 50% 50%; 298 | $custom-control-indicator-box-shadow: inset 0 .25rem .25rem rgba($black,.1); 299 | $custom-control-indicator-disabled-bg: $gray-200; 300 | $custom-control-description-disabled-color: $gray-600; 301 | $custom-control-indicator-checked-color: $white; 302 | $custom-control-indicator-checked-bg: theme-color("primary"); 303 | $custom-control-indicator-checked-box-shadow: none; 304 | $custom-control-indicator-focus-box-shadow: 0 0 0 1px $body-bg, $input-btn-focus-box-shadow; 305 | $custom-control-indicator-active-color: $white; 306 | $custom-control-indicator-active-bg: lighten(theme-color("primary"), 35%); 307 | $custom-control-indicator-active-box-shadow: none; 308 | $custom-checkbox-indicator-border-radius: $border-radius; 309 | $custom-checkbox-indicator-icon-checked: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='#{$custom-control-indicator-checked-color}' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3E%3C/svg%3E"), "#", "%23"); 310 | $custom-checkbox-indicator-indeterminate-bg: theme-color("primary"); 311 | $custom-checkbox-indicator-indeterminate-color: $custom-control-indicator-checked-color; 312 | $custom-checkbox-indicator-icon-indeterminate: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3E%3Cpath stroke='#{$custom-checkbox-indicator-indeterminate-color}' d='M0 2h4'/%3E%3C/svg%3E"), "#", "%23"); 313 | $custom-checkbox-indicator-indeterminate-box-shadow: none; 314 | $custom-radio-indicator-border-radius: 50%; 315 | $custom-radio-indicator-icon-checked: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='#{$custom-control-indicator-checked-color}'/%3E%3C/svg%3E"), "#", "%23"); 316 | $custom-select-padding-y: .375rem; 317 | $custom-select-padding-x: .75rem; 318 | $custom-select-height: $input-height; 319 | $custom-select-indicator-padding: 1rem; 320 | $custom-select-line-height: $input-btn-line-height; 321 | $custom-select-color: $input-color; 322 | $custom-select-disabled-color: $gray-600; 323 | $custom-select-bg: $input-bg; 324 | $custom-select-disabled-bg: $gray-200; 325 | $custom-select-bg-size: 8px 10px; 326 | $custom-select-indicator-color: $gray-200; 327 | $custom-select-indicator: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3E%3Cpath fill='#{$custom-select-indicator-color}' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E"), "#", "%23"); 328 | $custom-select-border-width: $input-btn-border-width; 329 | $custom-select-border-color: $input-border-color; 330 | $custom-select-border-radius: $border-radius; 331 | $custom-select-focus-border-color: lighten(theme-color("primary"), 25%); 332 | $custom-select-focus-box-shadow: inset 0 1px 2px rgba($black, .075), 0 0 5px rgba($custom-select-focus-border-color, .5); 333 | $custom-select-font-size-sm: 75%; 334 | $custom-select-height-sm: $input-height-sm; 335 | $custom-file-height: $input-height; 336 | $custom-file-width: 14rem; 337 | $custom-file-focus-box-shadow: 0 0 0 .075rem $white, 0 0 0 .2rem theme-color("primary"); 338 | $custom-file-padding-y: $input-btn-padding-y; 339 | $custom-file-padding-x: $input-btn-padding-x; 340 | $custom-file-line-height: $input-btn-line-height; 341 | $custom-file-color: $input-color; 342 | $custom-file-bg: $input-bg; 343 | $custom-file-border-width: $input-btn-border-width; 344 | $custom-file-border-color: $input-border-color; 345 | $custom-file-border-radius: $input-border-radius; 346 | $custom-file-box-shadow: $input-box-shadow; 347 | $custom-file-button-color: $custom-file-color; 348 | $custom-file-button-bg: $input-group-addon-bg; 349 | $custom-file-text: (placeholder: (en: "Choose file..."),button-label: (en: "Browse")); 350 | 351 | 352 | // 353 | //Dropdowns 354 | // 355 | 356 | $dropdown-min-width: 10rem; 357 | $dropdown-padding-y: .5rem; 358 | $dropdown-spacer: .125rem; 359 | $dropdown-bg: $white; 360 | $dropdown-border-color: rgba($black,.15); 361 | $dropdown-border-width: $border-width; 362 | $dropdown-divider-bg: $gray-200; 363 | $dropdown-box-shadow: 0 .5rem 1rem rgba($black,.175); 364 | $dropdown-link-color: $gray-900; 365 | $dropdown-link-hover-color: darken($gray-900, 5%); 366 | $dropdown-link-hover-bg: $gray-100; 367 | $dropdown-link-active-color: $component-active-color; 368 | $dropdown-link-active-bg: $component-active-bg; 369 | $dropdown-link-disabled-color: $gray-600; 370 | $dropdown-item-padding-y: .25rem; 371 | $dropdown-item-padding-x: 1.5rem; 372 | $dropdown-header-color: $gray-600; 373 | 374 | 375 | // 376 | //ZindexMasterList 377 | // 378 | 379 | $zindex-dropdown: 1000; 380 | $zindex-sticky: 1020; 381 | $zindex-fixed: 1030; 382 | $zindex-modal-backdrop: 1040; 383 | $zindex-modal: 1050; 384 | $zindex-popover: 1060; 385 | $zindex-tooltip: 1070; 386 | 387 | 388 | // 389 | //Navs 390 | // 391 | 392 | $nav-link-padding-y: .5rem; 393 | $nav-link-padding-x: 1rem; 394 | $nav-link-disabled-color: $gray-600; 395 | $nav-tabs-border-color: $gray-600; 396 | $nav-tabs-border-width: $border-width; 397 | $nav-tabs-border-radius: $border-radius; 398 | $nav-tabs-link-hover-border-color: $gray-200; 399 | $nav-tabs-link-active-color: $gray-100; 400 | $nav-tabs-link-active-bg: $body-bg; 401 | $nav-tabs-link-active-border-color: #dddddd; 402 | $nav-pills-border-radius: $border-radius; 403 | $nav-pills-link-active-color: $component-active-color; 404 | $nav-pills-link-active-bg: $component-active-bg; 405 | 406 | 407 | // 408 | //Navbar 409 | // 410 | 411 | $navbar-padding-y: $spacer; 412 | $navbar-padding-x: $spacer; 413 | $navbar-brand-font-size: $font-size-lg; 414 | $nav-link-height: ($font-size-base * $line-height-base + $nav-link-padding-y * 2); 415 | $navbar-brand-height: $navbar-brand-font-size * $line-height-base; 416 | $navbar-brand-padding-y: ($nav-link-height - $navbar-brand-height) / 2; 417 | $navbar-toggler-padding-y: .25rem; 418 | $navbar-toggler-padding-x: .75rem; 419 | $navbar-toggler-font-size: $font-size-lg; 420 | $navbar-toggler-border-radius: $btn-border-radius; 421 | $navbar-dark-color: rgba($white,.5); 422 | $navbar-dark-hover-color: rgba($white,.75); 423 | $navbar-dark-active-color: $white; 424 | $navbar-dark-disabled-color: rgba($white,.25); 425 | $navbar-dark-toggler-icon-bg: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='#{$navbar-dark-color}' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"), "#", "%23"); 426 | $navbar-dark-toggler-border-color: rgba($white,.1); 427 | $navbar-light-color: rgba($black,.5); 428 | $navbar-light-hover-color: rgba($black,.7); 429 | $navbar-light-active-color: rgba($black,.9); 430 | $navbar-light-disabled-color: rgba($black,.3); 431 | $navbar-light-toggler-icon-bg: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='#{$navbar-light-color}' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"), "#", "%23"); 432 | $navbar-light-toggler-border-color: rgba($black,.1); 433 | 434 | 435 | // 436 | //Pagination 437 | // 438 | 439 | $pagination-padding-y: .5rem; 440 | $pagination-padding-x: .75rem; 441 | $pagination-padding-y-sm: .25rem; 442 | $pagination-padding-x-sm: .5rem; 443 | $pagination-padding-y-lg: .75rem; 444 | $pagination-padding-x-lg: 1.5rem; 445 | $pagination-line-height: 1.25; 446 | $pagination-color: $white; 447 | $pagination-bg: $gray-900; 448 | $pagination-border-width: $border-width; 449 | $pagination-border-color: $gray-900; 450 | $pagination-hover-color: $white; 451 | $pagination-hover-bg: theme-color("primary"); 452 | $pagination-hover-border-color: theme-color("primary"); 453 | $pagination-active-color: $white; 454 | $pagination-active-bg: theme-color("primary"); 455 | $pagination-active-border-color: theme-color("primary"); 456 | $pagination-disabled-color: $gray-800; 457 | $pagination-disabled-bg: $gray-900; 458 | $pagination-disabled-border-color: $gray-900; 459 | 460 | 461 | // 462 | //Jumbotron 463 | // 464 | 465 | $jumbotron-padding: 2rem; 466 | $jumbotron-bg: $gray-900; 467 | 468 | 469 | // 470 | //Cards 471 | // 472 | 473 | $card-spacer-y: .75rem; 474 | $card-spacer-x: 1.25rem; 475 | $card-border-width: $border-width; 476 | $card-border-radius: $border-radius; 477 | $card-border-color: rgba($black,.125); 478 | $card-inner-border-radius: calc(#{$card-border-radius} - #{$card-border-width}); 479 | $card-cap-bg: rgba($black,.03); 480 | $card-bg: $gray-900; 481 | $card-img-overlay-padding: 1.25rem; 482 | $card-group-margin: ($grid-gutter-width / 2); 483 | $card-deck-margin: $card-group-margin; 484 | $card-columns-count: 3; 485 | $card-columns-gap: 1.25rem; 486 | $card-columns-margin: $card-spacer-y; 487 | 488 | 489 | // 490 | //Tooltips 491 | // 492 | 493 | $tooltip-max-width: 200px; 494 | $tooltip-color: $black; 495 | $tooltip-bg: $white; 496 | $tooltip-opacity: .9; 497 | $tooltip-padding-y: 3px; 498 | $tooltip-padding-x: 8px; 499 | $tooltip-margin: 0; 500 | $tooltip-arrow-width: 5px; 501 | $tooltip-arrow-height: 5px; 502 | $tooltip-arrow-color: $tooltip-bg; 503 | 504 | 505 | // 506 | //Popovers 507 | // 508 | 509 | $popover-bg: $white; 510 | $popover-max-width: 276px; 511 | $popover-border-width: $border-width; 512 | $popover-border-color: rgba($black,.2); 513 | $popover-box-shadow: 0 .25rem .5rem rgba($black,.2); 514 | $popover-header-bg: darken($popover-bg, 3%); 515 | $popover-header-color: $black; 516 | $popover-header-padding-y: .5rem; 517 | $popover-header-padding-x: .75rem; 518 | $popover-body-color: $gray-900; 519 | $popover-body-padding-y: $popover-header-padding-y; 520 | $popover-body-padding-x: $popover-header-padding-x; 521 | $popover-arrow-width: .8rem; 522 | $popover-arrow-height: .4rem; 523 | $popover-arrow-color: $popover-bg; 524 | $popover-arrow-outer-color: fade-in($popover-border-color, .05); 525 | 526 | 527 | // 528 | //Badges 529 | // 530 | 531 | $badge-font-size: 75%; 532 | $badge-font-weight: $font-weight-bold; 533 | $badge-padding-y: .4em; 534 | $badge-padding-x: .6em; 535 | $badge-border-radius: $border-radius; 536 | $badge-pill-padding-x: .6em; 537 | $badge-pill-border-radius: 10rem; 538 | 539 | 540 | // 541 | //Modals 542 | // 543 | 544 | $modal-inner-padding: 15px; 545 | $modal-dialog-margin: 10px; 546 | $modal-dialog-margin-y-sm-up: 30px; 547 | $modal-title-line-height: $line-height-base; 548 | $modal-content-bg: $gray-900; 549 | $modal-content-border-color: rgba($black,.2); 550 | $modal-content-border-width: $border-width; 551 | $modal-content-box-shadow-xs: 0 3px 9px rgba($black,.5); 552 | $modal-content-box-shadow-sm-up: 0 5px 15px rgba($black,.5); 553 | $modal-backdrop-bg: $black; 554 | $modal-backdrop-opacity: .5; 555 | $modal-header-border-color: $gray-700; 556 | $modal-footer-border-color: $modal-header-border-color; 557 | $modal-header-border-width: $modal-content-border-width; 558 | $modal-footer-border-width: $modal-header-border-width; 559 | $modal-header-padding: 15px; 560 | $modal-lg: 800px; 561 | $modal-md: 500px; 562 | $modal-sm: 300px; 563 | $modal-transition: transform .3s ease-out; 564 | 565 | 566 | // 567 | //Alerts 568 | // 569 | 570 | $alert-padding-y: .75rem; 571 | $alert-padding-x: 1.5rem; 572 | $alert-margin-bottom: 1rem; 573 | $alert-border-radius: $border-radius; 574 | $alert-link-font-weight: $font-weight-bold; 575 | $alert-border-width: $border-width; 576 | 577 | 578 | // 579 | //ProgressBars 580 | // 581 | 582 | $progress-height: 1rem; 583 | $progress-font-size: ($font-size-base * .75); 584 | $progress-bg: $gray-200; 585 | $progress-border-radius: $border-radius; 586 | $progress-box-shadow: inset 0 .1rem .1rem rgba($black,.1); 587 | $progress-bar-color: $white; 588 | $progress-bar-bg: theme-color("primary"); 589 | $progress-bar-animation-timing: 1s linear infinite; 590 | $progress-bar-transition: width .6s ease; 591 | 592 | 593 | // 594 | //ListGroup 595 | // 596 | 597 | $list-group-bg: $gray-900; 598 | $list-group-border-color: rgba($black,.125); 599 | $list-group-border-width: $border-width; 600 | $list-group-border-radius: $border-radius; 601 | $list-group-item-padding-y: .75rem; 602 | $list-group-item-padding-x: 1.25rem; 603 | $list-group-hover-bg: $gray-700; 604 | $list-group-active-color: $component-active-color; 605 | $list-group-active-bg: $component-active-bg; 606 | $list-group-active-border-color: $list-group-active-bg; 607 | $list-group-disabled-color: $gray-600; 608 | $list-group-disabled-bg: $list-group-bg; 609 | $list-group-action-color: $gray-100; 610 | $list-group-action-hover-color: $list-group-action-color; 611 | $list-group-action-active-color: $body-color; 612 | $list-group-action-active-bg: $gray-700; 613 | 614 | 615 | // 616 | //Images 617 | // 618 | 619 | $thumbnail-padding: .25rem; 620 | $thumbnail-bg: $body-bg; 621 | $thumbnail-border-width: $border-width; 622 | $thumbnail-border-color: $gray-600; 623 | $thumbnail-border-radius: $border-radius; 624 | $thumbnail-box-shadow: 0 1px 2px rgba($black,.075); 625 | $thumbnail-transition: all .2s ease-in-out; 626 | 627 | 628 | // 629 | //Figures 630 | // 631 | 632 | $figure-caption-font-size: 90%; 633 | $figure-caption-color: $gray-600; 634 | 635 | 636 | // 637 | //Breadcrumbs 638 | // 639 | 640 | $breadcrumb-padding-y: .75rem; 641 | $breadcrumb-padding-x: 1.5rem; 642 | $breadcrumb-item-padding: .5rem; 643 | $breadcrumb-margin-bottom: 1rem; 644 | $breadcrumb-bg: $gray-900; 645 | $breadcrumb-divider-color: $gray-300; 646 | $breadcrumb-active-color: $gray-100; 647 | $breadcrumb-divider: "/"; 648 | 649 | 650 | // 651 | //Carousel 652 | // 653 | 654 | $carousel-control-color: $white; 655 | $carousel-control-width: 15%; 656 | $carousel-control-opacity: .5; 657 | $carousel-indicator-width: 30px; 658 | $carousel-indicator-height: 3px; 659 | $carousel-indicator-spacer: 3px; 660 | $carousel-indicator-active-bg: $white; 661 | $carousel-caption-width: 70%; 662 | $carousel-caption-color: $white; 663 | $carousel-control-icon-width: 20px; 664 | $carousel-control-prev-icon-bg: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='#{$carousel-control-color}' viewBox='0 0 8 8'%3E%3Cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E"), "#", "%23"); 665 | $carousel-control-next-icon-bg: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='#{$carousel-control-color}' viewBox='0 0 8 8'%3E%3Cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E"), "#", "%23"); 666 | $carousel-transition: transform .6s ease; 667 | 668 | 669 | // 670 | //Close 671 | // 672 | 673 | $close-font-size: $font-size-base * 1.5; 674 | $close-font-weight: $font-weight-bold; 675 | $close-color: $gray-100; 676 | $close-text-shadow: 0 1px 0 $white; 677 | 678 | 679 | // 680 | //Code 681 | // 682 | 683 | $code-font-size: 90%; 684 | $code-padding-y: .2rem; 685 | $code-padding-x: .4rem; 686 | $code-color: #ff3535; 687 | $code-bg: $gray-100; 688 | $kbd-color: $white; 689 | $kbd-bg: $gray-900; 690 | $pre-color: $gray-200; 691 | $pre-scrollable-max-height: 340px; 692 | 693 | 694 | // 695 | //Extra SASS variables 696 | // 697 | 698 | $link-border-width: 0px; 699 | $link-border-style: solid; 700 | $link-border-color: transparent; 701 | $link-hover-border-width: 0px; 702 | $link-hover-border-style: solid; 703 | $link-hover-border-color: transparent; 704 | $link-font-size: inherit; 705 | $link-font-weight: inherit; 706 | $link-background-color: transparent; 707 | $link-hover-background-color: transparent; 708 | $link-footer-color: $link-color; 709 | $link-footer-decoration: $link-decoration; 710 | $link-footer-hover-color: $link-hover-color; 711 | $link-footer-hover-decoration: $link-hover-decoration; 712 | $paragraph-color: inherit; 713 | $paragraph-bold-text-weight: bolder; 714 | $paragraph-bold-text-color: inherit; 715 | $btn-text-transform: none; 716 | $btn-font-size: $font-size-base; 717 | $btn-font-size-lg: $font-size-lg; 718 | $btn-font-size-sm: $font-size-sm; 719 | $btn-background-image: none; 720 | $btn-hover-background-image: none; 721 | $btn-border-width: $border-width; 722 | $navbar-nav-link-padding-y: 0.5rem; 723 | $navbar-nav-link-padding-x: 0.8rem; 724 | $navbar-nav-link-text-transform: none; 725 | $navbar-nav-link-font-size: inherit; 726 | $navbar-nav-link-font-weight: $font-weight-bold; 727 | --------------------------------------------------------------------------------