哈喽小伙伴们~,2020年还剩最后15天
今天我们来一个年终福利文章。
社会层面:
2020年不过是一个普通的年份,却经历了与往常大不相同的12月,366天,8784小时,5207040分,31622400秒,世界人民一起经历了比2003年非典严重狡猾的病毒,直到现在世界疫情依然很严重!从任何方面,2020都是坎坷的一年,不论是是个人,或者是国家。通过这次疫情我更加热爱我的祖国,我们祖国的疫情全面得到控制,而国外却愈演愈烈,他们面对疫情不知所措乱成一锅粥。
今年国庆我很荣幸到了天安门广场,说不出来的一种自豪和骄傲。我们中国人民真的站起来了,我为我是中国人而骄傲!
己亥末,庚子春,荆楚大疫,染者数万计,众惶恐,举国防,皆闭户,南山镇守江南都,率白衣郎中数万抗之,且九州一心,月余,疫尽去,国泰民安。-摘自网络。
生活层面:
小伙伴,一直催我更新技术文章,最近实在是太忙,不瞒大家说,我开始带团队了,已经不是一个人在战斗了。一个人的时候可以没事的时候写写博客,那个时候感觉有很多很多时间也有很多很多精力去给大家更新博客,而现在多多少少有些力不从心了。只要我更新出来的东西,必须给读者负责,前一段时间我一个朋友在某博客学习某一个技术点,结果就是不出结果,最后一看写的博客少一行代码。我们既然敢写,就是敢负责到底。
福利分享:
今天给大家分享由我呢吧完全自主搭建一个完整技术工程,上才艺~
Java技术栈
- Spring Boot
- MyBatis Plus(含逆向工程)
- MySql
- Redis
- WebSocket
- Quartz
- Influx DB时序数据库
- Shiro+JWT
- P6Spy+SL4J
- fastDfs文件上传
- Swagger接口文档
- Hutool工具类
- 自定义注解
- 自定义异常
一、猿码优创工程简单使用:
1、swagger-ui使用:
1.1访问页面地址:http://127.0.0.1:12001/swagger-ui.html
1.2、使用方法:
接口注释:
@ApiOperation(value = "首页", notes = "访问测试", httpMethod = "GET")
参数注释:
@ApiParam(name = "名字", value = "名字", required = true) String name
1.3、配置方法:
private ApiInfo apiInfo() {
return new ApiInfo(
"猿码优创管理API文档",
"猿码优创管理API文档",
"2.0",
null,
new Contact("kingyifan", "https://cnbuilder.cn/", "itw@tom.com"),
"Apache 2.0", "https://www.apache.org/licenses/LICENSE-2.0.html", Collections.emptyList());
}
2、权限使用(流程:用户登录===》获取token===》请求头携带token===》请求接口===》校验权限===》返回结果):
2.0、Token配置文件:
2.1、登录:
请求参数:
form-data:
account=admin
password=admin
返回参数:
{
"msg": "登录成功(Login Success.)",
"code": 200,
"data": {
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjdXJyZW50VGltZU1pbGxpcyI6IjE2MDY0NDg3MDM1NzciLCJleHAiOjE2MDY0OTE5MDMsImFjY291bnQiOiJhZG1pbiJ9.nI_J4K7UtuHISwM3weGYKd65p3VhD7LSqcuCnqKa8A0"
}
}
登录以后会自动加请求头响应参数:
登录代码:
/**
* 登录授权
*
* @param account
* @param password
* @author 猿码优创 website:https://cnbuilder.cn/ Email:itw@tom.com
*/
@PostMapping("/login")
@ApiOperation(value = "用户", notes = "登录", httpMethod = "GET")
public R login(
@ApiParam(name = "account", value = "账号", required = true) @RequestParam(value = "account", required = true) String account,
@ApiParam(name = "password", value = "密码", required = true) @RequestParam(value = "password", required = true) String password,
HttpServletResponse httpServletResponse) {
Map reMap = new HashMap<>();
// 查询数据库中的帐号信息account
User userTemp = userService.getOne(new QueryWrapper<User>().eq("account", account));
if (userTemp == null) {
return R.error(500, "该帐号不存在(The account does not exist.)");
}
// 密码进行AES解密
String key = AesCipherUtil.deCrypto(userTemp.getPassword());
// 因为密码加密是以帐号+密码的形式进行加密的,所以解密后的对比是帐号+密码
if (key.equals(account + password)) {
// 清除可能存在的Shiro权限信息缓存
if (JedisUtil.exists(Constant.PREFIX_SHIRO_CACHE + account)) {
JedisUtil.delKey(Constant.PREFIX_SHIRO_CACHE + account);
}
//判斷redis是否存在 存在直接用相同的。这个可以避免同样的账号两个人登录 把另一个人给踢出 TODO:如果一个账号只能一个登录可以去掉这段代码~
String rtoken = (String) JedisUtil.getObject(Constant.EXISTENCE_TOKEN + account);
if (rtoken != null) {
httpServletResponse.setHeader("Authorization", rtoken);
httpServletResponse.setHeader("Access-Control-Expose-Headers", "Authorization");
reMap.put("token", rtoken);
return R.ok(HttpStatus.OK.value(), "登录成功(Login Success.)", reMap);
}
// 设置RefreshToken,时间戳为当前时间戳,直接设置即可(不用先删后设,会覆盖已有的RefreshToken)
String currentTimeMillis = String.valueOf(System.currentTimeMillis());
JedisUtil.setObject(Constant.PREFIX_SHIRO_REFRESH_TOKEN + account, currentTimeMillis, Integer.parseInt(refreshTokenExpireTime));
// 从Header中Authorization返回AccessToken,时间戳为当前时间戳
String token = JwtUtil.sign(account, currentTimeMillis);
//存放redis
JedisUtil.setObject(Constant.EXISTENCE_TOKEN + account, token, Integer.parseInt(accessTokenExpireTime));
reMap.put("token", token);
httpServletResponse.setHeader("Authorization", token);
httpServletResponse.setHeader("Access-Control-Expose-Headers", "Authorization");
return R.ok("登录成功(Login Success.)").data(reMap);
}
return R.error(500, "帐号或密码错误(Account or Password Error.)");
}
游客模式(不带Token也可以访问接口):
需要Token放行接口:
2.2、请求API接口:
不传Token:
传输错误Token(项目封装了异常捕获统一返回,如果不喜欢这样提示这样可以自行更改哈。):
传输正确Token
没有权限:
DB赋值权限(目前没做admin全部权限,有兴趣大家可以自己做一下哈):
有权限访问:
代码:
加一行注解即可:自己匹配权限
@RequiresPermissions(logical = Logical.AND, value = {"user:menus"})
代码样例:
@GetMapping(value = "/menus")
@ApiOperation(value = "获取菜单", notes = "获取菜单", httpMethod = "GET")
@SystemLogAnnotation(businessType = "用户", methodDetail = "获取菜单")
@RequiresPermissions(logical = Logical.AND, value = {"user:menus"})
public R menus() {
QueryWrapper<SysMenu> qr = new QueryWrapper<>();
//根据用户查询菜单权限
List<SysMenu> list = iSysMenuService.list(qr);
//配置
TreeNodeConfig treeNodeConfig = new TreeNodeConfig();
// 自定义属性名 都要默认值的
treeNodeConfig.setWeightKey("sort");
treeNodeConfig.setIdKey("id");
// 最大递归深度
treeNodeConfig.setDeep(3);
//转换器
List<Tree<String>> treeNodes = TreeUtil.build(list, "-1", treeNodeConfig,
(treeNode, tree) -> {
tree.setId(treeNode.getId().toString());
tree.setParentId(treeNode.getParentid().toString());
tree.setWeight(treeNode.getSort());
tree.setName(treeNode.getName());
// 扩展属性 ...
tree.putExtra("isJump", treeNode.getIsJump() == null || treeNode.getIsJump() == 0 ? false : true);
tree.putExtra("icon", treeNode.getIcon() == null ? "" : treeNode.getIcon());
tree.putExtra("url", treeNode.getUrl());
});
return R.ok().data(treeNodes);
}
2.3、获取用户信息(必须过jwt拦截器传Token,才可以获取。):
根据Token自己解析用户信息
代码:
final注入:
private final UserUtil userUtil;这种final方式注入需要在controller或者service加@RequiredArgsConstructor
普通注入:
@Autowired
private UserUtil userUtil;
直接使用:
User user = userUtil.getUser();
ps:权限框架集成参考:https://gitee.com/dolyw/ShiroJwt
3、influxDB:
3.0、influxDB简介和配置:
简介:
配置:
3.1、influxDB使用:
新增数据:没有database(数据库)和measurement(可以理解成表)自动会创建
//新增时序数据库数据
Map<String, String> tagsMap = new HashMap<>();//默认自己加索引
Map<String, Object> fieldsMap = new HashMap<>();//默认不加索引
tagsMap.put("name", "猿码优创");
tagsMap.put("website", "www.cnbuilder.cn");
tagsMap.put("Email", "itw@tom.com");
fieldsMap.put("author", "kingyifan");
influxDBConnect.insert("user", tagsMap, fieldsMap);
保存:
/**
* 插入
*
* @param measurement 表
* @param tags 标签
* @param fields 字段
*/
public void insert(String measurement, Map<String, String> tags, Map<String, Object> fields) {
Point.Builder builder = Point.measurement(measurement);
// 纳秒时会出现异常信息:partial write: points beyond retention policy dropped=1
// builder.time(System.nanoTime(), TimeUnit.NANOSECONDS);
builder.time(System.currentTimeMillis() / 1000, TimeUnit.SECONDS);
builder.tag(tags);
builder.fields(fields);
log.info("influxDB insert data:[{}]", builder.build().toString());
influxDB.write(database, "", builder.build());
}
插入成功:
查询数据 最后加tz('Asia/Shanghai')指定时区
PS:influxDB和mysql数据库不一样。
mysql返回的数据:
key:value
influxDb返回数据:
columns=[time, Email, author, name, website],
values=[[2020-12-17T10:16:39Z, itw@tom.com, kingyifan, 猿码优创, www.cnbuilder.cn]]]
返回数据:
解析代码:
代码:
/**
* 解析influx返回数据
*
* @param queryResult
* @return
*/
public static List<Map<String, Object>> convertQueryResult(QueryResult queryResult) {
List<Map<String, Object>> maps = new LinkedList<>();
if (queryResult.getResults().get(0).getSeries() == null || queryResult.getResults().get(0).getSeries().size() == 0) {
return null;
}
for (QueryResult.Series series : queryResult.getResults().get(0).getSeries()) {
List<String> columns = series.getColumns();
List<List<Object>> values = series.getValues();
for (List<Object> objects : values) {
Map map = new HashMap();
for (int i = 0; i < columns.size(); i++) {
String c = columns.get(i);
if (c.equals("time")) {
map.put(c, objects.get(i).toString().replace("+08:00", ""));
continue;
}
map.put(c, objects.get(i));
}
if (series.getTags() != null) {
for (String key : series.getTags().keySet()) {
map.put(key, series.getTags().get(key));
}
}
maps.add(map);
}
}
return maps;
}
4、定时任务Quartz:
4.1、使用:
注解:
@Scheduled(cron = "0/1 * * * * ? ")
ps:七子表达式在线生成地址:https://cron.qqe2.com/
5、登录拦截日志(已经实现)
5.1、使用:
拦截用户操作日志:谁在什么时间干了什么什么事最后结果是什么?没干成原因是什么?:
CREATE TABLE `sys_user_log` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`business_type` varchar(20) DEFAULT NULL COMMENT '业务类型',
`method_detail` varchar(50) DEFAULT NULL COMMENT '接口备注',
`ip` varchar(20) DEFAULT NULL COMMENT '请求ip',
`method_name` varchar(200) DEFAULT NULL COMMENT '接口方法',
`url` varchar(200) DEFAULT NULL COMMENT '接口url',
`params` text COMMENT '请求参数',
`sys_user_id` int(11) DEFAULT NULL COMMENT '用户id',
`sys_user_name` varchar(50) DEFAULT NULL COMMENT '操作人名称',
`excute_time` bigint(20) DEFAULT NULL COMMENT '执行耗时',
`create_time` datetime DEFAULT NULL COMMENT '操作时间',
`return_result` text COMMENT '返回结果',
`error_detail` text COMMENT '错误原因',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4;
用到了Spring的Aop环绕拦截(后期可以加mq)。
package cn.cnbuilder.manage.common.aspect;
import cn.cnbuilder.manage.busi.service.ISysUserLogService;
import cn.cnbuilder.manage.common.entity.R;
import cn.cnbuilder.manage.shiro.util.UserUtil;
import cn.cnbuilder.manage.busi.entity.SysUserLog;
import cn.cnbuilder.manage.common.annotation.SystemLogAnnotation;
import cn.cnbuilder.manage.common.utils.IpUtil;
import cn.cnbuilder.manage.shiro.model.entity.User;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Date;
/**
* 日志切面类
*
* @author 猿码优创 website:https://cnbuilder.cn/ Email:itw@tom.com
*/
@Aspect
@Component
public class LogAspect {
@Autowired
private UserUtil userUtil;
@Autowired
private ISysUserLogService sysUserLogService;
/**
* 定义一个切入点.
* 解释下:
* <p>
* ~ 第一个 * 代表任意修饰符及任意返回值.
* ~ 第二个 * 定义在web包或者子包
* ~ 第三个 * 任意方法
* ~ .. 匹配任意数量的参数.
*/
@Pointcut("execution(* cn.cnbuilder.manage.*.controller..*(..))")
public void logPointcut() {
}
@Pointcut("@annotation(cn.cnbuilder.manage.common.annotation.SystemLogAnnotation)")
public void executePointCut() {
}
@org.aspectj.lang.annotation.Around("logPointcut()")
public Object doAround(ProceedingJoinPoint joinPoint) {
//获取请求
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
SysUserLog sysUserLog = new SysUserLog();
long start = System.currentTimeMillis();
Object result = null;
// 操作模块和方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
SystemLogAnnotation annotation = signature.getMethod().getAnnotation(SystemLogAnnotation.class);
try {
//返回结果
result = joinPoint.proceed();
} catch (Throwable e) {
//保存
sysUserLog.setErrorDetail(e.getMessage());
e.printStackTrace();
//返回错误信息
result = R.error(500, e.getMessage());
} finally {
try {
//结束时间
long end = System.currentTimeMillis();
//执行时间
sysUserLog.setExcuteTime(end - start);
//y业务类型
sysUserLog.setBusinessType(annotation == null ? "" : annotation.businessType());
//操作时间
sysUserLog.setCreateTime(new Date());
//ip地址
sysUserLog.setIp(IpUtil.getIpAddr(request));
//接口方法
sysUserLog.setMethodName(joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
//请求参数
sysUserLog.setParams(Arrays.toString(joinPoint.getArgs()));
//返回结果
sysUserLog.setReturnResult(result == null ? "" : result.toString());
User user = userUtil.aroundGetUser();
//用户id
sysUserLog.setSysUserId(user.getId());
//用戶名
sysUserLog.setSysUserName(user.getUsername());
//请求url
sysUserLog.setUrl(request.getRequestURI());
//方法备注
sysUserLog.setMethodDetail(annotation == null ? "" : annotation.methodDetail());
sysUserLogService.save(sysUserLog);
} catch (Exception e) {
e.printStackTrace();
} finally {
//不管保存不保存日志都需要返回结果
return result;
}
}
}
}
使用了自定义注解:
添加使用注解:
@SystemLogAnnotation(businessType = "用户", methodDetail = "获取菜单")
自定义注解代码(可以自行添加别的参数):
package cn.cnbuilder.manage.common.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义注解
*
* @author 猿码优创 website:https://cnbuilder.cn/ Email:itw@tom.com
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SystemLogAnnotation {
// 业务类型
String businessType() default "";
//方法名称
String methodDetail() default "";
}
6、Redis使用(目前用的redis连接池,后期可以更换成redisTemplate):
6.1、配置文件:
存值:
JedisUtil.setObject("name", "wangyifan");
取值:
JedisUtil.getObject("name").toString();
更多使用详见JedisUtil工具类
7、WebSocket使用
正常发送消息:
群发消息:
具体配置信息我原来写过文章,请移步:https://blog.cnbuilder.cn/archives/SpringBoot_websocket
8、MyBatisPlus逆向工程
修改成自己的连接等配置,执行main方法,就会生成我们需要的controller、service、mapper、xml等文件
8.1、修改数据库配置:
// ======================数据源配置========================
DataSourceConfig dsc = new DataSourceConfig();
dsc.setDbType(DbType.MYSQL); //设置数据库类型,
dsc.setUrl("jdbc:mysql://xxx.xxx.xxx.xxx:xx/ymyc_manage?serverTimezone=GMT%2B8"); //指定数据库
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("root");
autoGenerator.setDataSource(dsc);
8.2、修改创建的表
//设置生成的表名,这里使用exClude来进行排除,写入null,表示生成所有表。
strategyConfig.setExclude(null);
//设置表 可变个数的形参 String数组
strategyConfig.setInclude("t_user",""xxx");
8.3、作者:
gc.setAuthor("猿码优创");// 作者
8.4、包的路径:
pc.setParent("cn.cnbuilder.manage.busi");
终、、以上奉献给你们的年终福利~
码云地址:https://gitee.com/it-wangyifan/ymyc_manage
鼓励作者写出更好的技术文档,就请我喝一瓶哇哈哈哈哈哈哈哈。。你们的赞助决定我更新的速度哦!
微信:
支付宝:
感谢一路支持我的人。。。。。
Love me and hold me
QQ:69673804(16年老号)
EMAIL:itw@tom.com
友链交换
如果有兴趣和本博客交换友链的话,请按照下面的格式在评论区进行评论,我会尽快添加上你的链接。
网站名称:猿码优创
网站地址:http://blog.cnbuilder.cn
网站描述:年少是你未醒的梦话,风华是燃烬的彼岸花。
网站Logo/头像: [头像地址](https://blog.cnbuilder.cn/upload/2018/7/avatar20180720144536200.jpg)
欢迎关注猿码优创(联系小优优进内部群哦,新鲜技术优先更新):