前言
采用Erupt框架 + amis前台框架 快速搭建项目程序
【目前开放访问:前台链接】(没做完,但组队功能做完了)
- 首先吹爆Erupt框架!!这个框架简直是后台开发利器,你甚至不需要先建表,直接拿来跑就出现了后台管理页面,利用代码生成反向生成实体类,直接映射生成了数据库,配合jpa,熟悉的话1小时不用一个简单的系统包括后台包括全套api就实现了。让你把时间放到具体业务上!!
- 其次就是Amis低代码前端框架!!这个前台我是配合semantic一起弄的,我前台只是仅限于改点别人页面,这次第一次完全独立自己实现了一套定制的前台,虽然说不上很美观,但至少框架出来了。后面打算简单学下vue配合amis感觉可以起飞。
这两个框架现在github/gitee虽说不上热门,以我现有的开发感受来说,觉得是两个后面会起来的黑马。难在初学者入门,用起来后是真的起飞,下面简单看下我的开发过程吧。~
一、框架结构
目前项目开发周期:5月中旬到6.10号大半个月
主要是学习一些新技术:技术原理简单,但实际用起来还是不是一个概念层级的,断断续续把一些技术成功对接进来,也还有很多欠缺,后面还会继续完善这个项目的【项目跟进】
因为分离的项目,所以后面可能会用vue+amis重构前台,或者安卓手机端或者鸿蒙;
二、界面展示
流体式布局+电脑手机端适配
前台界面:
组队详细界面
通用后台管理框架
后台代码界面
| 实体类 | 工具类 | 控制层类 | 拦截器及配置类 |
|---|---|---|---|
前台代码
| Json配置界面 | 主菜单主写jquery控制器 |
|---|---|
分布式部署/资源存放
| 静态资源存放github用cdn引用> 减轻前台服务器负担 | 图片托管存gitee> 减轻后台服务器图片请求负担 |
|---|---|
| 前台代码存放Gitee仓库 | 后台打jar包+mysql数据库部署自己服务器 |
三、数据库设计
ER流程图
省略属性
数据库关联
四、框架使用介绍
业务展示层:Erupt后台通用框架 + Amis百度前台通用框架 + Semantic通用前台组件库
交互规范层:[ restful接口规范 + 阿里java开发手册规范]
框架具体使用后面会在总结|总结后更新到下方
Erupt后台管理 -- 基于注解开发管理页面[详细 待更新]
Amis百度前台框架 -- 基于json开发前台界面[详细 待更新]
什么是 amis
amis 是一个低代码前端框架,它使用 JSON 配置来生成页面,可以减少页面开发工作量,极大提升效率。快速开发页面模板。
Semantic通用组件库[详细 待更新]
通过semantic提供好的丰富组件,基于amis构建的框架上构建内容,相当于amis是建房子,而semantic就是家具库。
五、技术点梳理
- 本系统仅采用qq登录,解决用户密码登录注册困难一问题,做到扫码即登录,登录即使用
- 技术点按照业务流程来梳
流程及技术点
qq登录 -- 用户绑定 -- 生成令牌 -- 组队请求[jwt校验 + 图片存储] -- 邮箱通知
qq登录[JustAuth支持]
业务流程
qq请求登录 -- 后台controller层封装返回qq登录链接 -- 前台跳转登录链接 -- 登录请求发送到腾讯 -- 腾讯验证后调用回调地址 --后台controller层响应处理数据绑定验证用户 -- 生成权限+用户打包发送给前台 -- 前台获取储存
绑定用户流程
相关代码:
@RequestMapping("/callback/qq")
//QQ登录回调请求
public Result login(AuthCallback callback) {
Object obj = null;
try{
AuthRequest authRequest = getAuthRequest();
obj = authRequest.login(callback);
}catch (Exception e){
return Result.error("登录异常");
}
//通过qq回调返回后台捆绑用户
JSONObject jsonObject = JSONObject.parseObject(JsonUtil.objectToJson(obj));
JSONObject data = JSONObject.parseObject(JsonUtil.objectToJson(jsonObject.get("data")));
System.out.println(data);
//验证用户
User haveUser =null;
User newUser = new User();
haveUser = userRepository.findUserByUuid(data.getString("uuid"));
if(haveUser!= null && haveUser.getId()!=null){
newUser = haveUser;
}else{
newUser.setInstitute("这个人很懒,什么都没留下");
newUser.setRegisterTime(new Date());
newUser.setUuid(data.getString("uuid"));
newUser.setGender(data.getString("gender"));
newUser.setHeadImg(data.getString("avatar"));
newUser.setNickName(data.getString("nickname"));
newUser = userRepository.save(newUser);
}
//验证必要信息
HashMap<String,Object> map = new HashMap<>();
String token = UserJsonUtil.UserToJwt(newUser);
map.put("token",token);
map.put("user",newUser);
if (newUser.getQq() == null || newUser.getEmail() == null || newUser.getRealName() == null
|| newUser.getUserId() == null) {
System.out.println(map);
return Result.build(200,"请补充必要信息",map);
}
return Result.success(map);
//JSONObject json = (JSONObject)JSONObject.toJSON(authRequest.login(callback));
//处理权限
}
困难点:
- qq开放平台审核注册 需要域名+审核
- 应用没上线前qq开放平台只支持审核域名回调,本地测试困难[解决方法用host 修改本地域名解析] + nginx端口转发实现
jwt+拦截认证
业务流程
给定能标识特定用户获取响应资源的内容body区 -- 通过密钥签名加密 -- 发送给用户 -- 用户需要请求数据时携带headers里面发送 -- 服务器解析确认返回相应请求
解决技术问题
传统项目存在用户认证问题,关于怎么让服务器识别用户,存服务器的session会增加服务器负担,简单存用户端的cookie容易被篡改;
需要一种解决方案那就是jwt,通过存放密钥在服务端和加密处理,发送给用户的令牌token唯一且不能被篡改且只有服务端有密钥匹配。
相关代码
签发token[校验和生成省略,可自行百度]
public static String createJWT(String id, String subject, long ttlMillis) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
SecretKey secretKey = generalKey();
JwtBuilder builder = Jwts.builder().setId(id).setSubject(subject) // 主题
.setIssuer("404name") // 签发者
.setIssuedAt(now) // 签发时间
.signWith(signatureAlgorithm, secretKey); // 签名算法以及密匙
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
builder.setExpiration(expDate); // 过期时间
}
return builder.compact();
}
拦截认证
通过拦截器拦截每一个请求校验header是否通过
配置intercaptor拦截所有请求
public class AuthInterceptor implements HandlerInterceptor {
@Autowired
private UserRepository userRepository;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
//配置拦截器实现放行逻辑
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
}
//返回错误信息
private static void setReturn(HttpServletResponse response, int status, String msg) throws IOException {
}
}
配置config放行指定路径不经过header校验
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Bean
public AuthInterceptor authInterceptor() {
return new AuthInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 放行路径
List<String> patterns = new ArrayList();
registry.addInterceptor(authInterceptor())
.addPathPatterns("/api/**")
.excludePathPatterns("/api/login")
.excludePathPatterns("/api/get/**");
}
}
图片存储
指定外部oss存储[阿里云、七牛云、git仓库]
这里选择gitee仓库,先开好仓库 -- 配置密钥 -- java模拟http请求发送上传请求 -- 返回访问链接存数据库
@Override
public String upLoad(InputStream inputStream, String path) {
//根据存储地区创建上传对象
System.out.println(inputStream + " " + path);
//设置转存到Gitee仓库参数
String paramImgFile = Base64.encode(inputStream);
Map<String, Object> paramMap = new HashMap<String, Object>();
paramMap.put("access_token", GiteeImgBed.ACCESS_TOKEN);
paramMap.put("message", GiteeImgBed.ADD_MESSAGE);
paramMap.put("content", paramImgFile);
//转存文件路径
String targetDir =GiteeImgBed.PATH + path;
String requestUrl = String.format(GiteeImgBed.CREATE_REPOS_URL, GiteeImgBed.OWNER,
GiteeImgBed.REPO_NAME, targetDir);
System.out.println("Url" + requestUrl);
//工具类模拟post请求
String resultJson = HttpUtil.post(requestUrl, paramMap);
return getUrlFromJson(resultJson);//自己实现从json中提取指定的字符串
}
邮件发送****
模拟请求即可,配置email工具类-- 提前开放邮箱email端口 -- 配置好邮箱模板 -- 封装使用即可;
EmailUtil.sendemail(接受邮箱,标题,内容模板);
效果:
六、遇到的问题及解决
跨域问题
一、问题:
跨域请求中包含自定义header字段时,浏览器console报错。
Request header field xfilesize is not allowed by Access-Control-Allow-Headers
二、原因:
包含自定义header字段的跨域请求,浏览器会先向服务器发送OPTIONS请求,探测该服务器是否允许自定义的跨域字段。
如果允许,则继续实际的POST/GET正常请求,否则,返回标题所示错误。
所以需要在后台添加过滤器对他进行回应
package com.jfly.apps.restful.springmvc;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
public class CORSInterceptor implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) servletResponse;
String origin = (String) servletRequest.getRemoteHost() + ":" + servletRequest.getRemotePort();
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with,authorization");
response.setHeader("Access-Control-Allow-Credentials", "true");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
且再启动函数前面加上注解@ServletComponentScan扫描此过滤器即可。