项目源代码:https://git.acwing.com/lzy612/kob
页面
进行模块的划分,还是通过项目去驱动技术
,哪缺啥技术去学什么技术web
、app
、小程序端
,通用存档
:用来保存你的代码并且当你遇到一些错误的时候,可以很方便的进行回滚代码,放在云端很安全、方便
同步不同机器上的代码
:比用U盘、硬盘更加装B,更加方便,pull一下就都下来了
网址:git下载地址(网上搜教程即可)
先进入Git Bash,然后进到家目录(cd),输入ssh-keygen生成秘钥,然后进入.ssh目录下,查看id_rsa.pub下的内容,复制下来
然后进入AcGit注册并登录,在SSH秘钥中将复制的内容粘进去,点击添加秘钥即可
选择你的项目位置然后创建好你的目录,专业来说的应该要在项目中创建一个readme.md来做一个介绍,然后在你的项目目录下右键Git Bash here,然后输入git init进行仓库初始化,初始化成功后,应该在文件夹下能看到一个.git隐藏文件,然后git add . 、git commit -m “创建项目”
进入AcGit创建一个新的项目与你在本地项目目录的文件名一致
复制AcGit中的git config 那两段代码(去掉–global)到Git Bash中粘贴,设置好作者名和邮箱
复制git remote add origin [email protected]:xxx/kob.git这段代码到Git Bash中连接好仓库
在Git Bash中输入git push然后将弹出来的代码复制下来并执行,将本地仓库的推送到远程仓库,在远程仓库你的项目看见了readme.md就算成功
至此,git环境就配置完成了,工欲善其事,必先利其器(其他操作可bing或者报名Linux基础课)
这里一定要看好一定要选择Maven仓库,啊!!!!!
大佬教程,大佬一站式解决
这里也有好多问题,我搞了好几个小时,最后是把node.js版本调低了16.15.1,vue用的版本高些5.0多,然后管理员模式进入kob目录下,最后vue ui,创建项目才成功!!!!!!(在kob目录下)
然后点击插件把这两个添加了
去依赖的地方,安装两个依赖一个jquery、一个bootstrap
然后点击任务、serve、运行、运行成功后点击输出,点击那个localhost的网址
出现以下界面就成功了
同理再创建一个acapp在kob目录下
这里只用添加一个vuex
然后git add 加 commit一套操作
用vscode打开web文件,修改src/router/index.js文件,将这两个Hash删除掉
这时候点击Home、About来回切换过程中,地址栏中的#就不会出现了
删除一些没用的代码,AboutView.vue、HomeView.vue、HelloWorld.vue
将APP.vue中的style中的代码删除,nav中的代码也删除掉
加一段话div中的hello world
删除index.js中一段代码
最后变成这样干净的页面
跨域问题y总说就是localhost和127.0.01不同,浏览器有个安全机制,不让随便访问,解决办法上网搜代码抄代码
App.vue代码如下:
Bot昵称:{{ bot_name }}
Bot战力:{{ bot_rating}}
在idea后端的src/main/java/com/kob/backend/config下创建CorsConfig,代码如下(完美解决!)
package com.kob.backend.config;
import org.springframework.context.annotation.Configuration;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Configuration
public class CorsConfig implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
HttpServletRequest request = (HttpServletRequest) req;
String origin = request.getHeader("Origin");
if(origin!=null) {
response.setHeader("Access-Control-Allow-Origin", origin);
}
String headers = request.getHeader("Access-Control-Request-Headers");
if(headers!=null) {
response.setHeader("Access-Control-Allow-Headers", headers);
response.setHeader("Access-Control-Expose-Headers", headers);
}
response.setHeader("Access-Control-Allow-Methods", "*");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Credentials", "true");
chain.doFilter(request, response);
}
@Override
public void init(FilterConfig filterConfig) {
}
@Override
public void destroy() {
}
}
最后为了美观搞一个背景图片,将背景图片放到web/src/assets目录下,在App.vue下面加上这个(一个是图片的url,一个是图片全覆盖的属性)
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="#">Navbar w/ texta>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarText" aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon">span>
button>
<div class="collapse navbar-collapse" id="navbarText">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="#">Homea>
li>
<li class="nav-item">
<a class="nav-link" href="#">Featuresa>
li>
<li class="nav-item">
<a class="nav-link" href="#">Pricinga>
li>
ul>
<span class="navbar-text">
Navbar text with an inline element
span>
div>
div>
nav>
<template>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="#">King Of Botsa>
<div class="collapse navbar-collapse" id="navbarText">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link" aria-current="page" href="#">对战a>
li>
<li class="nav-item">
<a class="nav-link active" href="#">对局列表a>
li>
<li class="nav-item">
<a class="nav-link" href="#">排行榜a>
li>
ul>
<ul class="navbar-nav">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarScrollingDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
lzy
a>
<ul class="dropdown-menu" aria-labelledby="navbarScrollingDropdown">
<li><a class="dropdown-item" href="#">我的Botsa>li>
<li><hr class="dropdown-divider">li>
<li><a class="dropdown-item" href="#">退出a>li>
ul>
li>
ul>
div>
div>
nav>
template>
<script>
script>
<style scoped>
style>
对战
import { createRouter, createWebHistory } from 'vue-router'
import PkIndexView from '../views/pk/PkIndexView';
import RanklistIndexView from '../views/ranklist/RanklistIndexView';
import RecordIndexView from '../views/record/RecordIndexView';
import UserBotIndexView from '../views/user/bot/UserBotIndexView';
import NotFound from '../views/404/NotFound';
const routes = [
{
path: "/",
name: "home",
redirect: "/pk/"
},
{
path: "/pk/",
name: "pk_index",
component: PkIndexView
},
{
path: "/ranklist/",
name: "ranklist_index",
component: RanklistIndexView
},
{
path: "/record/",
name: "record_index",
component: RecordIndexView
},
{
path: "/user/bot/",
name: "userbot_index",
component: UserBotIndexView
},
{
path: "/404/",
name: "404",
component: NotFound
},
{
path: "/:catchAll(.*)",
redirect: "/404/"
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
对战
const AC_GAME_OBJECTS = [];
export class AcGameObject {
constructor(){
AC_GAME_OBJECTS.push(this);
// 当移动起来 需要一个 速度的概念 1s中移动几个像素 然后这里有推到使用时间
// 这一帧与上一帧 执行 之间的时间间隔
this.timedelta = 0;
//是否执行过
this.has_called_start = false;
}
start(){ // 只执行一次
}
update() { // 每一帧执行一次,除了第一帧之外
}
on_destory() { // 删除之前执行
}
destory() {
this.on_destory();
//用in的话遍历的是下标
for (let i in AC_GAME_OBJECTS){
const obj = AC_GAME_OBJECTS[i];
if(obj === this){
AC_GAME_OBJECTS.splice(i);
break;
}
}
}
}
let last_timestamp; //上一次执行的时刻
const step = timestamp => {
// 用of遍历的是值
for(let obj of AC_GAME_OBJECTS){
if(!obj.has_called_start){
obj.has_called_start = true;
obj.start();
}else{
obj.timedelta = timestamp - last_timestamp;
obj.update();
}
}
last_timestamp = timestamp;
requestAnimationFrame(step);
}
requestAnimationFrame(step);
import { AcGameObject } from "./AcGameObject";
export class GameMap extends AcGameObject{
// ctx画布 parent画布的父元素
constructor(ctx, parent){
super();
this.ctx = ctx;
this.parent = parent;
//绝对距离
this.L = 0;
}
start(){
}
update(){
this.render();
}
render(){
}
}
标签里面的一样大,这里y总设置的GameMap.vue的
是空的,这里的ctx初始就是空的,这个parent就是外面那个大的蓝色的区域,然后进行一个y总的算法计算,求出游玩的区域总能在parent中的正正好好出现的位置 render(){
// color_even是偶数格子 color_odd是奇数格子
const color_even = "#AAD751", color_odd = "#A2D149";
// 枚举格子填充颜色
for (let r = 0; r < this.rows; r++){
for (let c = 0; c < this.cols; c++){
if((r + c) % 2 == 0){
//选颜色
this.ctx.fillStyle = color_even;
}else{
this.ctx.fillStyle = color_odd;
}
//画方块
this.ctx.fillRect(c * this.L, r * this.L, this.L, this.L);
}
}
}
标签中依赖名 | 作用 |
---|---|
Spring Boot Starter JDBC | JDBC依赖 |
Project Lombok | 写pojo的注解 |
MySQL Connector/J | MySQL官方JDBC驱动程序 |
mybatis-plus-boot-starter | MyBatis的增强工具 |
mybatis-plus-generator | 代码生成器 |
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/kob?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
层 | 功能 |
---|---|
pojo层 | 将数据库中的表对应成Java中的Class |
mapper层(也叫Dao层) | 将pojo层的class中的操作,映射成sql语句 |
service层 | 写具体的业务逻辑,组合使用mapper中的操作 |
controller层 | 负责请求转发,接受页面过来的参数,传给Service处理,接到返回值,再传给页面 |
依赖 | 作用 |
---|---|
spring-boot-starter-security | Spring Security 是一个功能强大且高度可定制的身份验证和访问控制框架。 |
配置好依赖后,再次启动,当你访问任何一个url的时候就会出现这个(username是user,password是在springboot启动状态下面动态生成的)
修改SpringBoot安全访问配置,首先要创建一个UserDetailsServiceImpl类来继承UserDetailsService来接入数据库信息,然后再实现一个utils的工具类UserDetailsImpl来实现UserDetails。最后的逻辑就是在那个安全验证中输入你的账号和密码,如果在数据库中找到了username就将这个条字段作为通关令牌存起来,当退出的时候再删除
注:这里如果没有加密的话要使用的话在密码那个字段的属性中加一个{noop}
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
如果有人想通过修改userId然后获取其他人的权限的时候,这种方法是不现实的,因为他只知道userId而不知道秘钥,他只拥有userId加上加密后的字符串,秘钥通过反推得到的话,几乎是不可能的
依赖名 | 作用 |
---|---|
jjwt-api | |
jjwt-impl | |
jjwt-jackson |
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;
@Component
public class JwtUtil {
public static final long JWT_TTL = 60 * 60 * 1000L * 24 * 14; // 有效期14天
public static final String JWT_KEY = "SDFGjhdsfalshdfHFdsjkdsfds121232131afasdfac";
public static String getUUID() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
public static String createJWT(String subject) {
JwtBuilder builder = getJwtBuilder(subject, null, getUUID());
return builder.compact();
}
private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
SecretKey secretKey = generalKey();
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if (ttlMillis == null) {
ttlMillis = JwtUtil.JWT_TTL;
}
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
return Jwts.builder()
.setId(uuid)
.setSubject(subject)
.setIssuer("sg")
.setIssuedAt(now)
.signWith(signatureAlgorithm, secretKey)
.setExpiration(expDate);
}
public static SecretKey generalKey() {
byte[] encodeKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
return new SecretKeySpec(encodeKey, 0, encodeKey.length, "HmacSHA256");
}
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(jwt)
.getBody();
}
}
import com.kob.backend.mapper.UserMapper;
import com.kob.backend.pojo.User;
import com.kob.backend.service.impl.utils.UserDetailsImpl;
import com.kob.backend.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private UserMapper userMapper;
@Override
protected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException {
String token = request.getHeader("Authorization");
if (!StringUtils.hasText(token) || !token.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
token = token.substring(7);
String userid;
try {
Claims claims = JwtUtil.parseJWT(token);
userid = claims.getSubject();
} catch (Exception e) {
throw new RuntimeException(e);
}
User user = userMapper.selectById(Integer.parseInt(userid));
if (user == null) {
throw new RuntimeException("用户名未登录");
}
UserDetailsImpl loginUser = new UserDetailsImpl(user);
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginUser, null, null);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);
}
}
import com.kob.backend.config.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/user/account/token/", "/user/account/register/").permitAll()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.anyRequest().authenticated();
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
}
@TableId(type = IdType.AUTO)
,可以使用以下的语句重新创建表CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(100) ,
`password` varchar(100) ,
`photo` varchar(1000) ,
PRIMARY KEY (`id`)
)
package com.kob.backend.service.impl.user.account;
import com.kob.backend.pojo.User;
import com.kob.backend.service.impl.utils.UserDetailsImpl;
import com.kob.backend.service.user.account.LoginService;
import com.kob.backend.utils.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
// 认证管理器
private AuthenticationManager authenticationManager;
@Override
public Map<String, String> getToken(String username, String password) {
// 将输入进来的username和password封装成一个密码加密过后的对象
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(username, password);
// 该方法接收一个认证令牌对象,也就是认证请求作为参数,如果其中的信息匹配到目标账号,
// 则该方法返回同一个认证令牌对象,不过其中被认证过程填充了更多的账号信息,比如授权和用户详情等。
Authentication authenticate = authenticationManager.authenticate(authenticationToken);// 如果登录失败会自动报异常
UserDetailsImpl loginUser = (UserDetailsImpl)authenticate.getPrincipal();
User user = loginUser.getUser();
String jwt = JwtUtil.createJWT(user.getId().toString());
Map<String, String> map = new HashMap<>();
map.put("error_message", "success");
map.put("token", jwt);
return map;
}
}
package com.kob.backend.service.impl.user.account;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.kob.backend.mapper.UserMapper;
import com.kob.backend.pojo.User;
import com.kob.backend.service.user.account.RegisterService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class RegisterServiceImpl implements RegisterService {
@Autowired
private UserMapper userMapper;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public Map<String, String> register(String username, String password, String confirmedPassword) {
Map<String, String> map = new HashMap<>();
// 判断有没有username这个参数
if(username == null) {
map.put("error_message", "用户名不能为空");
return map;
}
// 判断有没有password和confirmedPassord这个参数
if(password == null || confirmedPassword == null) {
map.put("error_message", "密码不能为空");
return map;
}
// 删除前后的空格
username = username.trim();
// 判断username是否为空
if(username.length() == 0){
map.put("error_message", "用户名不能为空");
return map;
}
// 判断password是否为空
if(password.length() == 0 || confirmedPassword.length() == 0){
map.put("error_message", "密码不能为空");
return map;
}
// 判断用户名的长度
if(username.length() > 100){
map.put("error_message", "用户名长度不能大于100");
return map;
}
// 判断密码的长度
if(password.length() > 100 || confirmedPassword.length() > 100){
map.put("error_message", "密码长度不能大于100");
return map;
}
// 判断两次密码是否一致
if(!password.equals(confirmedPassword)){
map.put("error_message", "两次输入的密码不一致");
return map;
}
// 查询数据库中是否有这个用户名
// 定义一个条件查询器
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// 定义条件
queryWrapper.eq("username", username);
// 查询
List<User> users = userMapper.selectList(queryWrapper);
// 判断
if(!users.isEmpty()){
map.put("error_message", "用户名已经存在");
return map;
}
// 加密
String encodedPassword = passwordEncoder.encode(password);
String photo = "https://cdn.acwing.com/media/user/profile/photo/239860_lg_086619de6f.jpg";
User user = new User(null, username, password, photo);
int insert = userMapper.insert(user);
map.put("error_message", "注册成功");
return map;
}
}
原理就是将token放到localStorge中去,登录成功的时候放进去,退出的时清除掉,刷新的时候在登录页面做一层判断,如果能从localStorge中能获取到token,然后用token去调用一下getinfo请求,如果可以成功的话,那么就会自动跳转到主页咯
然后优化一个当你localStorage中有正确的token的时候,你刷新页面的时候,后面会闪一下那个登录页面,导航栏会闪一下那个登录注册,很不好看,所以我们要优化一下
CREATE TABLE `kob`.`bot` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` int NOT NULL,
`title` varchar(100) NULL,
`description` varchar(300) NULL,
`content` varchar(10000) NULL,
`rating` int NULL DEFAULT 1500,
`createtime` datetime NULL,
`modifytime` datetime NULL,
PRIMARY KEY (`id`)
);
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Bot {
@TableId(type = IdType.AUTO)
private Integer id;
private Integer userId;
private String title;
private String description;
private String content;
private Integer rating;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createtime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date modifytime;
}
public interface AddService {
public Map<String, String> add(Map<String, String> data);
}
public interface GetListService {
public List<Bot> getlist();
}
public interface RemoveService {
public Map<String, String> remove(Map<String, String> data);
}
public interface UpdateService {
public Map<String, String> update(Map<String, String> map);
}
@Service
public class AddServiceImpl implements AddService {
@Autowired
private BotMapper botMapper;
@Override
public Map<String, String> add(Map<String, String> data) {
UsernamePasswordAuthenticationToken authenticationToken =
(UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
UserDetailsImpl loginUser = (UserDetailsImpl)authenticationToken.getPrincipal();
User user = loginUser.getUser();
String title = data.get("title");
String description = data.get("dsecription");
String content = data.get("content");
Map<String, String> map = new HashMap<>();
if(title == null || title.length() == 0){
map.put("error_message", "标题不能为空");
return map;
}
if(title.length() > 100) {
map.put("error_message", "标题长度不能大于100");
return map;
}
if(description == null || description.length() == 0){
description = "这个用户很懒,什么也没写";
}
if(description.length() > 300){
map.put("error_message", "Bot描述长度不能大于300");
return map;
}
if(content == null || content.length() == 0){
map.put("error_message", "代码不能为空");
return map;
}
if(content.length() > 10000){
map.put("error_message", "代码长度不能大于100000");
return map;
}
Date date = new Date();
Bot bot = new Bot(null, user.getId(), title, description, content, 1500, date, date);
botMapper.insert(bot);
map.put("error_message", "success");
return map;
}
}
@Service
public class RemoveServiceImpl implements RemoveService {
@Autowired
private BotMapper botMapper;
@Override
public Map<String, String> remove(Map<String, String> data) {
int bot_id = Integer.parseInt(data.get("bot_id"));
System.out.println(bot_id);
UsernamePasswordAuthenticationToken authenticationToken =
(UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
UserDetailsImpl loginUser = (UserDetailsImpl) authenticationToken.getPrincipal();
User user = loginUser.getUser();
Bot bot = botMapper.selectById(bot_id);
Map<String, String> map = new HashMap<>();
if(bot == null){
map.put("error_message", "Bot不存在或已经删除");
return map;
}
if(!bot.getUserId().equals(user.getId())){
map.put("error_message", "没有权限删除该Bot");
return map;
}
botMapper.deleteById(bot_id);
map.put("error_message", "success");
return map;
}
}
@Service
public class UpdateServiceImpl implements UpdateService {
@Autowired
private BotMapper botMapper;
@Override
public Map<String, String> update(Map<String, String> data) {
UsernamePasswordAuthenticationToken authenticationToken =
(UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
UserDetailsImpl loginUser= (UserDetailsImpl) authenticationToken.getPrincipal();
User user = loginUser.getUser();
int bot_id = Integer.parseInt(data.get("bot_id"));
String title = data.get("title");
String description = data.get("description");
String content = data.get("content");
Map<String, String> map = new HashMap<>();
if(title == null || title.length() == 0){
map.put("error_message", "标题不能为空");
return map;
}
if(title.length() > 100) {
map.put("error_message", "标题长度不能大于100");
return map;
}
if(description == null || description.length() == 0){
description = "这个用户很懒,什么也没写";
}
if(description.length() > 300){
map.put("error_message", "Bot描述长度不能大于300");
return map;
}
if(content == null || content.length() == 0){
map.put("error_message", "代码不能为空");
return map;
}
if(content.length() > 10000){
map.put("error_message", "代码长度不能大于100000");
return map;
}
Bot bot = botMapper.selectById(bot_id);
if(bot == null){
map.put("error_message", "Bot不存在或已被删除");
return map;
}
if(!bot.getUserId().equals(user.getId())){
map.put("error_message", "没有权限修改该Bot");
return map;
}
Bot new_bot = new Bot(bot.getId(), user.getId(), title, description, content, bot.getRating(), bot.getCreatetime(), new Date());
botMapper.updateById(new_bot);
map.put("error_message", "success");
return map;
}
}
我的Bot
Bot名称
创建时间
操作
{{ bot.title }}
{{ bot.createtime}}
vue3-ace-editor
import { VAceEditor } from 'vue3-ace-editor';
import ace from 'ace-builds';
import { VAceEditor } from 'vue3-ace-editor';
import ace from 'ace-builds';
<VAceEditor
v-model:value="botadd.content"
@init="editorInit"
lang="c_cpp"
theme="textmate"
style="height: 300px" />
"container">
"row">
"col-3">
"card" style="margin-top: 20px;">
"card-body" >
<img :src="$store.state.user.photo" alt="" style="width:100%; ">
"col-9">
"card" style="margin-top: 20px;">
"card-header">
"font-size: 140%">我的Bot
"modal fade" id="add-bot-btn" tabindex="-1">
"modal-dialog modal-xl">
"modal-content">
"modal-header">
"modal-title fs-5">创建Bot
"modal-body">
"mb-3">
"botadd.title" type="email" class="form-control" id="add-bot-title" placeholder="请输入Bot名称">
"mb-3">
"mb-3">
v-model:value="botadd.content" @init="editorInit" lang="c_cpp" theme="textmate" style="height: 300px" />
"modal-footer">
"error-message">{{ botadd.error_message }}
"card-body">
"table table-striped table-hover">
Bot名称
创建时间
操作
"bot in bots" :key="bot.id">
{{ bot.title }}
{{ bot.createtime}}
"modal fade" :id="'remove-bot-modal' + bot.id" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
"modal-dialog">
"modal-content">
"modal-header">
"modal-title fs-5">删除
"modal-body">
"font-size: 150%px">是否删除!
"modal-footer">
"modal fade" :id="'update-bot-modal' + bot.id" tabindex="-1">
"modal-dialog modal-xl">
"modal-content">
"modal-header">
"modal-title fs-5">创建Bot
"modal-body">
"mb-3">
"bot.title" type="email" class="form-control" id="add-bot-title" placeholder="请输入Bot名称" >
"mb-3">
"mb-3">
v-model:value="bot.content" @init="editorInit" lang="c_cpp" theme="textmate" style="height: 300px" />
"modal-footer">
"error-message">{{ bot.error_message }}
超文本传输协议
,用于从 www 服务器传输超文本到本地浏览器的传输协议。http 是基于 tcp 协议的一个应用层的协议,由请求和相应构成;是一个无状态的协议。1、客户端和服务端建立连接,http开始工作;
2、建立连接后客户端发送请求给服务器;
3、服务器收到请求后,给予相应的响应信息;
4、客户端接收服务器返回的数据并在浏览器上展示,然后客户端和服务器连接断开。
第一次:建立连接,客户端发送 SYN 包(SYN=i)到服务器,并进入 SYN_SEND 状态,等待服务器确认;
第二次:服务器接收 SYN 包,确认客户的 SYN ,同时也发送一个 SYN 包(SYN=k)+ ACK 包(ACK = i+1)给客户端,服务器进入 SYN_RECV 状态;
第三次:客户端接收到服务器的 SYN+ACK 包之后,同时向服务器发送确认包 ACK 包(ACK = k+1),然后客户端和服务器进入ESTABLISHED 状态。
完成三次握手之后,客户端和服务器开始传输数据。
第一次:客户端发送一个 FIN(i) ,用来关闭客户端和服务器的数据传输,客户端进入 FIN_WAIT_1 状态;
第二次:服务器接收 FIN ,发送一个 ACK (i+1)给客户端,服务器进入 CLISE_WAIT 状态;
第三次:服务器发送一个 FIN (j) 给客户端用来关闭服务器到客户端的数据传输,服务器进入 LAST_ACK 状态;
第四次:客户端接收到 FIN 后,客户端进入 TIME_WAIT 状态,然后发送 ACK (j+1) 给服务器,校验完成之后服务器进入 CLOSED 状态。
websocket 是 H5 的提出的在单个 TCP 协议上进行全双工通讯的协议。它允许服务器主动向客户端推送数据,客户端和服务器只需要完成一次握手,两者之间就可以建立持久性的连接,并进行双向数据传输。
websocket 是基于 http 协议的。借助 http 协议来完成握手。
工作过程:
1、客户端发送 http 请求,经过三次握手,建立 TCP连接,在 http 请求里存放 websocket 支持的版本号等信息;
2、服务器接收请求,同样以 http 协议回应;
3、连接成功,客户端与服务器建立持久性的连接。
连接之后客户端和服务器之间就可以随时通讯,直到其中一方关闭连接。
都是 TCP 协议;
都使用 Request/Response 模型进行连接的建立;
websocket 是基于 http 的,他们的兼容性都很好;
在连接的建立过程中对错误的处理方式相同;
都可以在网络中传输数据。
websocket 是持久连接,http 是短连接;
websocket 的协议是以 ws/wss 开头,http 对应的是 http/https;
websocket 是有状态的,http 是无状态的;
websocket 连接之后服务器和客户端可以双向发送数据,http 只能是客户端发起一次请求之后,服务器才能返回数据;
websocket 是可以跨域的;
websocket 连接建立之后,数据的传输使用帧来传递,不再需要Request消息。
可参考:websocket
依赖名称 | 作用 |
---|---|
spring-boot-starter-websocket | 实现websocket的依赖 |
fastjson | 将java对象转化为json,将json转化为java对象 |
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
@Component
@ServerEndpoint("/websocket/{token}") // 注意不要以'/'结尾
public class WebSocketServer {
@OnOpen
public void onOpen(Session session, @PathParam("token") String token) {
// 建立连接
}
@OnClose
public void onClose() {
// 关闭链接
}
@OnMessage
public void onMessage(String message, Session session) {
// 从Client接收消息
}
@OnError
public void onError(Session session, Throwable error) {
error.printStackTrace();
}
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/websocket/**");
}
参考
方法注入@Component
@ServerEndpoint("/websocket/{token}") // 注意不要以'/'结尾
public class WebSocketServer {
// 静态变量存储所有链接
private static ConcurrentHashMap<Integer, WebSocketServer> users = new ConcurrentHashMap<>();
// 将session存在每一个user中,user持有session
private User user;
// websocket中的session 每个链接用session来维护的
private Session session = null;
// 下面要查询userId对应的数据,所以要注入一个usermapper,
private static UserMapper userMapper;
// 这里要注入的方式有些不同,注入方式好像是和WebSocket不是一个单例模式有关系
@Autowired
public void setUserMapper(UserMapper userMapper){
WebSocketServer.userMapper = userMapper;
}
@OnOpen
public void onOpen(Session session, @PathParam("token") String token) {
// 建立连接
this.session = session;
System.out.println("connected!");
// 这里先假设token是userId,后面会修改的
Integer userId = Integer.parseInt(token);
this.user = userMapper.selectById(userId);
users.put(userId, this);
}
@OnClose
public void onClose() {
// 关闭链接
System.out.println("disconnected!");
if(this.user != null){
users.remove(this.user.getId());
}
}
@OnMessage
public void onMessage(String message, Session session) {
// 从Client接收消息
System.out.println("receive message!");
}
@OnError
public void onError(Session session, Throwable error) {
error.printStackTrace();
}
public void sendMessage(String message){
// 加锁,异步通信
synchronized (this.session){
try{
// 发送信息的api
// 后端向当前链接发送信息
this.session.getBasicRemote().sendText(message);
}catch (IOException e){
e.printStackTrace();
}
}
}
}
public class Game {
final private Integer rows;
final private Integer cols;
final private Integer inner_walls_count;
final int [][] g;
final private static int[] dx = {-1, 0, 1, 0}, dy = {0, 1, 0, -1};
public Game(Integer rows, Integer cols, Integer inner_walls_count){
this.rows = rows;
this.cols = cols;
this.inner_walls_count =inner_walls_count;
this.g = new int[rows][cols];
}
public int[][] g(){
return g;
}
private boolean check_connectivity(int sx, int sy, int tx, int ty){
if(sx == tx && sy == ty) return true;
g[sx][sy] = 1;
for(int i = 0; i < 4; i++) {
int x = sx + dx[i], y = sy + dy[i];
if (x >= 0 && x < this.rows && y >= 0 && y < this.cols && g[x][y] == 0) {
if (check_connectivity(x, y, tx, ty)) {
g[sx][sy] = 0;
return true;
}
}
}
return false;
}
private boolean draw() {
for (int i = 0; i < this.rows; i++) {
for (int j = 0; j < this.cols; j++) {
g[i][j] = 0;
}
}
// 给左右两边加上障碍物
for (int r = 0; r < this.rows; r++) {
g[r][0] = g[r][this.cols - 1] = 1;
}
// 给上下两边加上障碍物
for (int c = 0; c < this.cols; c++) {
g[0][c] = g[this.rows - 1][c] = 1;
}
Random random = new Random();
// 随机生成障碍物
for (int i = 0; i < this.inner_walls_count / 2; i++) {
for (int j = 0; j < 1000; j++) {
int r = random.nextInt(this.rows);
int c = random.nextInt(this.cols);
if (g[r][c] == 1 || g[this.rows - 1 - r][this.cols - 1 - c] == 1) {
continue;
}
if (r == this.rows - 2 && c == 1 || r == 1 && c == this.cols - 2) {
continue;
}
g[r][c] = g[this.rows - 1 - r][this.cols - 1 - c] = 1;
break;
}
}
return check_connectivity(this.rows - 2, 1, 1, this.cols - 2);
}
public void createMap(){
for (int i = 0; i < 1000; i++) {
if(draw()){
break;
}
}
}
}
新建表
CREATE TABLE `kob`.`record` (
`id` int NOT NULL AUTO_INCREMENT,
`a_id` int NULL,
`a_sx` int NULL,
`a_sy` int NULL,
`b_id` int NULL,
`b_sx` int NULL,
`b_sy` int NULL,
`a_steps` varchar(1000) NULL,
`b_steps` varchar(1000) NULL,
`map` varchar(1000) NULL,
`loser` varchar(10) NULL,
`createtime` datetime NULL,
PRIMARY KEY (`id`)
);
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Record {
@TableId(type = IdType.AUTO)
private Integer id;
private Integer aId;
private Integer aSx;
private Integer aSy;
private Integer bId;
private Integer bSx;
private Integer bSy;
private String aSteps;
private String bSteps;
private String map;
private String loser;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
private Date createtime;
}
前面使用的匹配策略是一个傻瓜式的匹配,这里要实现用一个微服务来完成匹配的任务
依赖名称 | 作用 |
---|---|
spring-cloud-dependencies | springcloud的依赖 |
依赖名 | 作用 |
---|---|
joor-java-8 | 是一个java反射工具包, 极小(只有4个核心类)流式编程, 主要简化繁复的反射API使用, 没有另加反射缓存. |
package com.kob.botrunningsystem.utils;
import java.util.ArrayList;
import java.util.List;
public class Bot implements com.kob.botrunningsystem.utils.BotInterface{
static class Cell{
public int x, y;
public Cell(int x, int y){
this.x = x;
this.y = y;
}
}
@Override
public Integer nextMove(String input) {
String[] strs = input.split("#");
int[][] g = new int[13][14];
for (int i = 0, k = 0; i < 13; i++) {
for (int j = 0; j < 14; j++, k++) {
if(strs[0].charAt(k) == '1'){
g[i][j] = 1;
}
}
}
int aSx = Integer.parseInt(strs[1]), aSy = Integer.parseInt(strs[2]);
int bSx = Integer.parseInt(strs[4]), bSy = Integer.parseInt(strs[5]);
List<Cell> aCells = getCells(aSx, aSy, strs[3]);
List<Cell> bCells = getCells(bSx, bSy, strs[6]);
for (Cell c : aCells) {
g[c.x][c.y] = 1;
}
for (Cell c : bCells) {
g[c.x][c.y] = 1;
}
int[] dx = {-1, 0, 1, 0}, dy = {0, 1, 0, -1};
for (int i = 0; i < 4; i++) {
int x = aCells.get(aCells.size() - 1).x + dx[i];
int y = aCells.get(aCells.size() - 1).y + dy[i];
if(x >= 0 && x < 13 && y >= 0 && y < 14 && g[x][y] == 0){
System.out.println("方向是" + i);
return i;
}
}
return 0;
}
// 检测当前回合蛇的长度是否增加
private boolean check_tail_increasing(int step){
if(step <= 10) return true;
return step % 3 == 1;
}
// 这里我理解的就是 前端需要去渲染蛇所以才那么复杂,
// 后端只需要去描绘出来蛇在地图上的位置就可以
public List<Cell> getCells(int sx, int sy, String steps){
steps = steps.substring(1, steps.length() - 1);
List<Cell> res = new ArrayList<>();
int[] dx = {-1, 0, 1, 0}, dy = {0, 1, 0, -1};
int x = sx, y = sy;
int step = 0;
res.add(new Cell(x, y));
for(int i = 0; i < steps.length(); i++){
int d = steps.charAt(i) - '0';
x += dx[d];
y += dy[d];
res.add(new Cell(x, y));
if(!check_tail_increasing(++ step)){
res.remove(0);
}
}
return res;
}
}
<template>
<ContentField>
<table class="table table-striped table-hover" style="text-align: center">
<thead>
<tr>
<th>玩家</th>
<th>天梯分</th>
</tr>
</thead>
<tbody>
<tr v-for="user in users" :key="user.id" >
<td>
<img :src="user.photo" alt="" class="record-user-photo">
<span class="record-user-username">{{ user.username }}</span>
</td>
<td>
{{ user.rating }}
</td>
</tr>
</tbody>
</table>
<nav aria-label="...">
<ul class="pagination" style="float: right;">
<li class="page-item" @click="click_page(-2)">
<a class="page-link" href="#" >前一页</a>
</li>
<li v-for="page in pages" :class="'page-item ' + page.is_active" :key="page.number" @click="click_page(page.number)">
<a class="page-link" href="#" >{{ page.number }}</a>
</li>
<li class="page-item" @click="click_page(-1)">
<a class="page-link" href="#" >后一页</a>
</li>
</ul>
</nav>
</ContentField>
</template>
<script>
import ContentField from '../../components/ContentField';
import { useStore } from 'vuex';
import { ref } from 'vue';
import $ from 'jquery';
export default{
components: {
ContentField
},
setup(){
const store = useStore();
let current_page = 1;
let users = ref([]);
let total_users = 0;
let pages = ref([]);
const click_page = (page) =>{
if(page === -2) page = current_page - 1;
else if(page === -1) page = current_page + 1;
let max_pages = parseInt(Math.ceil(total_users / 3));
if(page >= 1 && page <= max_pages){
pull_page(page);
}
}
const update_pages = () => {
// ceil是向上取整的意思
let max_pages = parseInt(Math.ceil(total_users / 3));
let new_pages = [];
for(let i = current_page - 2; i <= current_page + 2; i++){
if(i >= 1 && i <= max_pages){
new_pages.push({
number: i,
is_active: i === current_page ? "active" : "",
});
}
}
pages.value = new_pages;
}
// 拉取某一页的函数
const pull_page = page =>{
current_page = page;
$.ajax({
url: "http://127.0.0.1:3000/ranklist/getlist/",
type: "get",
data:{
page,
},
headers: {
Authorization: "Bearer " + store.state.user.token,
},
success(resp){
users.value = resp.users;
total_users = resp.users_count;
update_pages();
},
error(resp){
console.log(resp);
}
});
}
pull_page(current_page);
return {
users,
pages,
click_page
}
}
}
</script>
<style scoped>
img.record-user-photo{
width: 5vh;
border-radius: 50%;
}
</style>
ssh [email protected]
若你已经登录过然后又重置了虚拟机要执行以下命令:ssh-keygen -R "你的远程服务器ip地址"
sudo su -
,输入你的密码,然后创建acs
用户,并分配给用户acs权限 adduser acs # 创建用户acs
usermod -aG sudo acs # 给用户acs分配sudo权限
AC Terminal
去配置免密登录,就是先进入到.ssh(没有就自己创建),然后生成秘钥ssh-keygen
,打开家目录下的.ssh/config(没有就自己创建),输入你想免密登录的服务器的信息,然后ssh-copy-id ServerName
,输入完密码后,就可以通过ssh ServerName
,直接登录到你的那台服务器上了Host myserver1
HostName IP地址或域名
User 用户名
AC Terminal
中的祖传代码拷贝过去scp .bashrc .vimrc .tmux.conf server_name: # server_name需要换成自己配置的别名
sudo apt-get update
sudo apt-get install tmux
sudo usermod -aG docker $USER
执行完此操作后,需要退出服务器,再重新登录回来,才可以省去sudo权限。
AC Terminal
中的/var/lib/acwing/docker/images中将django_lesson_1_0.tar
scp到你的服务器中scp django_lesson_1_0.tar springboot_server:
git bash here
,来到家目录中然后还是没有.ssh自己创建,然后进去后,生成秘钥ssh-keygen
,vim config
然后输入Host myserver1
HostName IP地址或域名
User 用户名
然后ssh-copy-id ServerName,输入完密码后,就可以通过ssh ServerName,直接登录到你的那台服务器上,若你已经登录过然后又重置了虚拟机要执行以下命令:ssh-keygen -R "你的远程服务器ip地址"
AC Terminal
中登录到你的服务器上解压django_lesson_1_0.tar
,查看镜像是否解压完成docker images
docker load -i django_lesson_1_0.tar
然后创建容器docker run -p 20000:22 -p 443:443 -p 80:80 -p 3000:3000 -p 3001:3001 -p 3002:3002 -itd --name kob_server django_lesson:1.0
,通过2000端口登录容器,后面的就是各种服务的端口,然后给这个容器起一个名字kob_server
最后加上镜像名字和版本号
然后进入到你的容器里面docker attach kob_server
,然后在容器里面创建一套刚才的用户见上方操作
,创建好新的acs用户后Ctrl+P Ctrl+q
将容器挂起(这样容器不会关闭),然后进入云平台去放开20000端口
回到AC Terminal
中配置免密登录,然后ssh-copy-id ServerName
上,ssh ServerName
到你的容器中,然后在本地配置免密登录步骤相同
,然后把祖传代码传到容器里面scp .tmux.conf .bashrc .vimrc springboot:
Host springboot
HostName ip
User acs
Port 20000
更新一下: sudo apt-get update
安装: sudo apt-get install mysql-server
启动: sudo service mysql start
登录mysql: sudo mysql -u root
设置root密码:ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY 'yourpasswd';
create databaseskob;
use kob;
创建表:m将以下sql语句复制到一个sql文件夹里面,然后通过命令在数据库中执行以下命令这个脚本就可了source /home/acs/create_table.sql
create table kob.bot
(
id int auto_increment
primary key,
user_id int not null,
title varchar(100) null,
description varchar(300) null,
content varchar(10000) null,
createtime datetime null,
modifytime datetime null
);
create table kob.record
(
id int auto_increment
primary key,
a_id int null,
a_sx int null,
a_sy int null,
b_id int null,
b_sx int null,
b_sy int null,
a_steps varchar(1000) null,
b_steps varchar(1000) null,
map varchar(1000) null,
loser varchar(10) null,
createtime datetime null
);
create table kob.user
(
id int auto_increment
primary key,
username varchar(100) null,
password varchar(100) null,
photo varchar(1000) null,
rating int default 1500 null
);
16. 然后安装java8,sudo apt-get install openjdk-8-jdk
<packaging>jarpackaging>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<configuration>
<mainClass>这里要修改成main方法的路径mainClass>
configuration>
<executions>
<execution>
<goals>
<goal>repackagegoal>
goals>
execution>
executions>
plugin>
plugins>
build>
websocket那个ws要变成wss然后后面吗不要加api那个
vue --version
,如果是低于5.0.8
就重新装一下高版本的,npm i -g @vue/[email protected]
/etc/nginx/nginx.config
,然后记得重新加载一下配置sudo /etc/init.d/nginx reload
,加载完了以后可以在本地启动一下vue的项目然后试一下,如果可以正常显示就是跨域问题解决了user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 768;
# multi_accept on;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
ssl_prefer_server_ciphers on;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
gzip on;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
server {
listen 80;
server_name app3935.acapp.acwing.com.cn;
rewrite ^(.*)$ https://${server_name}$1 permanent;
}
server {
listen 443 ssl;
server_name app3935.acapp.acwing.com.cn;
ssl_certificate cert/acapp.pem;
ssl_certificate_key cert/acapp.key;
ssl_session_timeout 5m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
charset utf-8;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
client_max_body_size 10M;
location /api {
proxy_pass http://127.0.0.1:3000;
}
location /websocket {
proxy_pass http://127.0.0.1:3000;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 36000s;
}
location / {
root /home/acs/kob/web;
index index.html;
try_files $uri $uri/ /index.html;
}
location /acapp {
alias /home/acs/kob/acapp;
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'https://www.acwing.com';
add_header 'Access-Control-Allow-Methods' 'GET, PUT, OPTIONS, POST, DELETE';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,X-Amz-Date';
add_header 'Access-Control-Max-Age' 86400;
add_header 'Content-Type' 'text/html; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
if ($request_method = 'PUT') {
add_header 'Access-Control-Allow-Origin' 'https://www.acwing.com';
add_header 'Access-Control-Allow-Methods' 'GET, PUT, OPTIONS, POST, DELETE';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,X-Amz-Date';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
}
if ($request_method = 'GET') {
add_header 'Access-Control-Allow-Origin' 'https://www.acwing.com';
add_header 'Access-Control-Allow-Methods' 'GET, PUT, OPTIONS, POST, DELETE';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,X-Amz-Date';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
}
}
}
}
vue
和vue-router
bootstrap
、@properjs/core
、vue3-ace-editor
、jquery
vue.config.js
,可以让vue3将项目打包成一个js文件和一个css文件。const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
configureWebpack: {
// No need for splitting
optimization: {
splitChunks: false
}
}
})
https://app****.acapp.acwing.com.cn/acapp/app.js
,可以看一下这个注:如果后面F12控制台报错myfunc不是一个函数就是将最后的一个括号删除了
#! /bin/bash
rm app.js app.css
mv *.js app.js
mv *.css app.css
updateload.sh(在本地文件的King Of Bots\kob\acapp中)
#! /bin/bash
scp dist/js/*.js springboot:kob/acapp/
scp dist/css/*.css springboot:kob/acapp/
ssh springboot 'cd kob/acapp && ./rename.sh'
这里我们发现我们的背景图片都没有正常的显示,是因为依赖和acwing里面的样式有冲突,所以要修改一下前端的样式
package com.kob.backend.service.impl.user.account.acwing;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.kob.backend.mapper.UserMapper;
import com.kob.backend.pojo.User;
import com.kob.backend.service.impl.user.account.acwing.utils.HttpCilentUtil;
import com.kob.backend.service.user.account.acwing.AcAppService;
import com.kob.backend.utils.JwtUtil;
import com.sun.org.glassfish.gmbal.NameValue;
import org.apache.http.HttpHeaders;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.time.Duration;
import java.util.*;
@Service
public class AcAppServiceImpl implements AcAppService {
private final static String appid = "4296";
private final static String appsecret = "11ad11a17a094434ba56b152b3f1bec7";
private final static String redirectUri = "https://app4296.acapp.acwing.com.cn/api/user/account/acwing/acapp/receive_code/";
private final static String applyAccessTokenUrl = "https://www.acwing.com/third_party/api/oauth2/access_token/";
private final static String getUserInfoUrl = "https://www.acwing.com/third_party/api/meta/identity/getinfo/";
private final static Random random = new Random();
@Autowired
private UserMapper userMapper;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
public JSONObject applyCode() {
JSONObject resp = new JSONObject();
resp.put("appid", appid);
try {
resp.put("redirect_uri", URLEncoder.encode(redirectUri, "UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
resp.put("result", "failed");
return resp;
}
resp.put("scope", "userinfo");
StringBuilder state = new StringBuilder();
for (int i = 0;i < 10; i++)
state.append((char) (random.nextInt(10) + '0'));
resp.put("state", state.toString());
resp.put("result", "success");
// 将state存储到redis中,后面供返回回来的state做比对,防止野鸡服务器
redisTemplate.opsForValue().set(state.toString(), "true");
// 设置一下10分钟过期
redisTemplate.expire(state.toString(), Duration.ofMinutes(10));
return resp;
}
@Override
public JSONObject receiveCode(String code, String state) {
JSONObject resp = new JSONObject();
resp.put("result", "failed");
if (code == null || state == null) return resp;
if (Boolean.FALSE.equals(redisTemplate.hasKey(state))) return resp;
redisTemplate.delete(state);
List<NameValuePair> nameValuePairs = new LinkedList<>();
nameValuePairs.add(new BasicNameValuePair("appid", appid));
nameValuePairs.add(new BasicNameValuePair("secret", appsecret));
nameValuePairs.add(new BasicNameValuePair("code", code));
String getString = HttpCilentUtil.get(applyAccessTokenUrl, nameValuePairs);
if (getString == null) return resp;
JSONObject getResp = JSONObject.parseObject(getString);
String accessToken = getResp.getString("access_token");
String openid = getResp.getString("openid");
if (accessToken == null || openid == null) return resp;
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("openid", openid);
List<User> users = userMapper.selectList(queryWrapper);
if(!users.isEmpty()){
User user = users.get(0);
String jwt = JwtUtil.createJWT(user.getId().toString());
resp.put("result", "success");
resp.put("jwt_token", jwt);
return resp;
}
nameValuePairs = new LinkedList<>();
nameValuePairs.add(new BasicNameValuePair("access_token", accessToken));
nameValuePairs.add(new BasicNameValuePair("openid", openid));
getString = HttpCilentUtil.get(getUserInfoUrl, nameValuePairs);
if (getString == null) return resp;
getResp = JSONObject.parseObject(getString);
String username = getResp.getString("username");
String photo = getResp.getString("photo");
User user = new User(
null,
username,
null,
photo,
1500,
openid
);
userMapper.insert(user);
String jwt = JwtUtil.createJWT(user.getId().toString());
resp.put("result", "success");
resp.put("jwt_token", jwt);
return resp;
}
}
package com.kob.backend.service.impl.user.account.acwing;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.kob.backend.mapper.UserMapper;
import com.kob.backend.pojo.User;
import com.kob.backend.service.impl.user.account.acwing.utils.HttpCilentUtil;
import com.kob.backend.service.user.account.acwing.WebService;
import com.kob.backend.utils.JwtUtil;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.time.Duration;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
@Service
public class WebAppServiceImpl implements WebService {
private final static String appid = "4296";
private final static String appsecret = "11ad11a17a094434ba56b152b3f1bec7";
private final static String redirectUri = "https://app4296.acapp.acwing.com.cn/user/account/acwing/web/receive_code/";
private final static String applyAccessTokenUrl = "https://www.acwing.com/third_party/api/oauth2/access_token/";
private final static String getUserInfoUrl = "https://www.acwing.com/third_party/api/meta/identity/getinfo/";
private final static Random random = new Random();
@Autowired
private UserMapper userMapper;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
public JSONObject applyCode() {
JSONObject resp = new JSONObject();
String encodeUrl = "";
try {
encodeUrl = URLEncoder.encode(redirectUri, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
resp.put("result", "failed");
return resp;
}
StringBuilder state = new StringBuilder();
for (int i = 0;i < 10; i++)
state.append((char) (random.nextInt(10) + '0'));
resp.put("state", state.toString());
resp.put("result", "success");
// 将state存储到redis中,后面供返回回来的state做比对,防止野鸡服务器
redisTemplate.opsForValue().set(state.toString(), "true");
// 设置一下10分钟过期
redisTemplate.expire(state.toString(), Duration.ofMinutes(10));
String applyCodeUrl = "https://www.acwing.com/third_party/api/oauth2/web/authorize/?appid=" + appid
+ "&redirect_uri=" + encodeUrl + "&scope=" + "userinfo" + "&state=" + state;
resp.put("apply_code_url", applyCodeUrl);
return resp;
}
@Override
public JSONObject receiveCode(String code, String state) {
JSONObject resp = new JSONObject();
resp.put("result", "failed");
if (code == null || state == null) return resp;
if (Boolean.FALSE.equals(redisTemplate.hasKey(state))) return resp;
redisTemplate.delete(state);
List<NameValuePair> nameValuePairs = new LinkedList<>();
nameValuePairs.add(new BasicNameValuePair("appid", appid));
nameValuePairs.add(new BasicNameValuePair("secret", appsecret));
nameValuePairs.add(new BasicNameValuePair("code", code));
String getString = HttpCilentUtil.get(applyAccessTokenUrl, nameValuePairs);
if (getString == null) return resp;
JSONObject getResp = JSONObject.parseObject(getString);
String accessToken = getResp.getString("access_token");
String openid = getResp.getString("openid");
if (accessToken == null || openid == null) return resp;
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("openid", openid);
List<User> users = userMapper.selectList(queryWrapper);
if(!users.isEmpty()){
User user = users.get(0);
String jwt = JwtUtil.createJWT(user.getId().toString());
resp.put("result", "success");
resp.put("jwt_token", jwt);
return resp;
}
nameValuePairs = new LinkedList<>();
nameValuePairs.add(new BasicNameValuePair("access_token", accessToken));
nameValuePairs.add(new BasicNameValuePair("openid", openid));
getString = HttpCilentUtil.get(getUserInfoUrl, nameValuePairs);
if (getString == null) return resp;
getResp = JSONObject.parseObject(getString);
String username = getResp.getString("username");
String photo = getResp.getString("photo");
if(username == null || photo == null){
return resp;
}
for(int i = 0; i < 100; i++){
QueryWrapper<User> usernamequeryWrapper = new QueryWrapper<>();
usernamequeryWrapper.eq("username", username);
if(userMapper.selectList(usernamequeryWrapper).isEmpty()) break;
username += (char)(random.nextInt(10) + '0');
if(i == 99) return resp;
}
User user = new User(
null,
username,
null,
photo,
1500,
openid
);
userMapper.insert(user);
String jwt = JwtUtil.createJWT(user.getId().toString());
resp.put("result", "success");
resp.put("jwt_token", jwt);
return resp;
}
}
完整代码地址https://git.acwing.com/lzy612/kob