工作上的笔记
重要:
- 遇到问题多思考是哪里的问题 是前端的问题还是后端的问题
- 遇到问题所思考是哪方面的,提问题要取核心,是业务还是代码写不来, 而不是直接说不会
- 遇到问题在请教别人的时候拿本子记下来关键的地方,正所谓好记性不如烂笔头
- 遇到问题在寻求别人帮忙的时候不要直接让别人来操作而是让他教你去找问题
- 再使用自己常用的开发工具的时候一定要熟练,就是记忆快捷键的使用提高开发效率
- 逻辑判断要想好 各种情况都要预料到
- 在BUG频发的时候不要心浮气躁,要稳住心态在合理时间内进行修改,如一天8小时,可以花2小时修改另外6小时写新的代码,并且注意优先级问题
- 光写代码不重要,重要的是业务理解能力也很出色
- 主动学习法,在即将做一件事情的时候学会主动给负责人讲解对于自己的思路,然后负责人会给你纠正.
- 进入一个新的环境,新的公司,一个形象很重要,不是外表而是能力,在测试bug还是写代码都保持一颗严谨的心,这是很重要的!这样别人在心里都给你慢慢定型一种形象!
- 做事多考虑一点点 严谨一点 凡事就会不一样!
代码要用sonarLint或sonarQube扫描,解决【严重程度】中所有问题
业务表的每个字段是什么业务含义,会用在什么地方,要吃透,查询时要注意看一下字段是否有带够(如状态或排序字段是否要用上
一.常见页面上的问题:
1.变量命名:要启用有业务含义的变量名称,不要用IDE生成的变量名
2.常量定义:不要有魔法值,请尽可能定义有Const接口中
3.Swagger注解:类、方法、参数和返回值,这几部分要完整
4.返回结构要用结构化对象封装数据,而不要直接用map传值,包括service返回controller的值时也一样
5.MVC层次要清楚,controller->service->mapper(dao),不要在controller层中直接调用mapper
进入测试阶段后,凡是不是自己本次明确需要修改的代码,不要“好心办坏事”地去扩大改修范围。
哪怕看到代码不合理甚至有bug,一定要向我和大强哥提出来,我们确认之后,还要通知测试,大家一致同意方可动手。
团队开发,这是个重要原则,大家务必要谨记。
JFinal笔记
- 使用缓存 解决了数据库的压力, 如更新很频繁每次都要查询就降低性能所以用这个更合适 第一次正常执行,将sql语句缓存了第二次直接运行结果
- #namespace 就是一个作用域 同一个文件里的sql如果存在一样 则用这个不影响 因为用的时候 #namespace.#sql
- Ret Ret 是 return value 的缩写 一般用于方法返回值或者在 controller 中 renderJson(ret) 主要是用来做返回值,用来返回操作处理的数据和状态的。 也用于服务器对客户端请求响应JSon数据通讯。
- 在Enjoy中数组中获取map的值 #(mmap[“name”]) 其中name要用双引号 而在map集合中,也是set(“mmap”,map)传值
- 静态常量访问是 语法是 包名+类名::常量
- 定义范围数组[1…10] 中间是两个小数点 多了要报错
- 国际化 先配置文件 存储只要是key值 如果要转换国际化 那么 则 如#(_res.get(“portal.五星推荐”)) 相应在配置文件key=name 在浏览器中在后面 ?_locale=en或zh ,在代码中可以使用i18nText(KEY);
- .在数据库中一个表已经存在一个ID,删除后依然在那, 外表看起是删除但在内存中只是系统给它打了一个标识,下次添加依然还是会从删除那个往后梯增,
- 所以在写代码的时候如果已经save保存过数据库,那么后面就不能再save()因为id已经存在,所以选择update更新,如果不确定有没有那么选择saveorUpdate();
- 使用Lombok这个插件
@Data注解==使用这个注解可以省去代码中大量的get()、 set()、 toString()等方法;
@Accessors== 注解用来配置lombok如何产生和显示getters和setters的方法。
@ToString==自动生成toString方法并且当我们新增一个成员变量后,需要再去维护这个方法的实现。那有没有这样一个注解,可以自动生成toString方法?答案就是@ToString。
@Slf4j==声明:如果不想每次都写private final Logger logger = LoggerFactory.getLogger(当前类名.class); 可以用注解@Slf4j;
-
在数据库中更新操作时,直接用model.update();即可,因为如果用service去update也会去调用这个,只是内部进行封装了而已
-
set只有重新渲染这个页面才可以 如果返回一个json就没办法渲染
-
postman中body中x-www-form… 是form表单提交,而旁边那个是form-data是文件提交
postman中Headers是模拟用户登录i key值取Cookie value取jessionid值 就可以模拟了
在postman中:
-
“notepad” cmd命令行迅速打开文本编辑器
-
不要在controller写逻辑性代码要精简, 数据库操作那些都放在service层 而这个service曾可以自定义,
-
多个数据库要放在事务里 因为如果上面保存如果失败那么下面就不会执行,就会直接pass掉,所以就放在事务里
-
不能在service层去调用service层会无穷调, 最好在Controller里去调用Service层的@SqlMethod(platform = PlatformEnum.CLIENT), 如果在Service层调用那么,就调用方法然后把sqlmethod的参数具体写在里面:
private Record getMstPresentRecord(String detail){
Record record = Db.template(sqlKey(PlatformEnum.CLIENT,"getMstPresentRecord"), Kv.by("detail", detail)).findFirst();
return record;
}
- 经常使用RAP接口查看响应参数内容,那么我们最终可以看到在浏览器F12然后NetWork查看请求方法里Headers最下面可以看到请求参数,而旁边的Response可以看到响应的内容是否符合要求
- 返回的时候只需要建一个Vo 是响应的内容 其中就是String类型啊 之类的 然后最终我们要的是将响应参数放进去然后通过RenderJson()返回
renderJson(Ret.ok(DATA, data).set(MSG,"充值成功"));
ClinetWalletCouponVo vo = new ClinetWalletCouponVo();
vo.setMoney(rechargeAmount.toString());
vo.setIntegral(rechargePoint.toString());
然后return返回vo就可以了 如果是多个数组那种 就要先new一个ArrList然后存储 最后返回这个list就可以了
- 如果关联几张表然后返回时Record类型 想要获取其中的字段则
Record record = Db.template(sqlKey(PlatformEnum.CLIENT,"getMstPresentRecord"), Kv.by("detail", detail)).findFirst();
那么就通过record.get("其中的字段")
- 在SQL中使用#if判断集合是否为空则可以使用isEmpty()跟notEmpty(),空的话返回true;
- New一个数据库相关操作就放在Service层,这样子好处就是层与层关系明确
- Controller层主要拿来判断和验证相关操作,Controller层要薄
- 提交代码一定要看清楚,不能把人家的代码改了(包括一个空格也是错误的),提交代码要看清楚不该提的也不能提
- 每写一个方法就要写注释,这样子才知道是什么意思
- 数据库空值判断处理(数据类型一定要对 如果是String类型可以用工具类Strutil.isBlank())
MyMoney myMoney = myMoneyService.findFirstByColumns(Columns.create(MyMoney.C_USER_ID, userId));
//数据库判断处理
BigDecimal money =null;
if (myMoney!=null){
money = myMoney.getBalance();
}
set("money", money);
- compareTo()用于比较 包装器类型 如果前者跟后者比较相等=0 大于>返回大于0的值小于<也返回小于0的值
- Join关联表
(1) JOIN: 如果表中有至少一个匹配,则返回行
(2) LEFT JOIN: 即使右表中没有匹配,也从左表返回所有的行
(3) RIGHT JOIN: 即使左表中没有匹配,也从右表返回所有的行
(4) FULL JOIN: 只要其中一个表中存在匹配,就返回行 - getRawObject()用于获取从http请求中获取数据,配合我们新建的vo请求 那么就可以直接获取到前端传过来的数据 用的时候vo.get类型(参数), 这是用于前端传的格式为JSON;获取到DATA 然后转为Object
这里就可以直接拿来用了
ManageAddWriterVo vo = getRawObject(ManageAddWriterVo.class);
这是建立在多个参数 如果只有一个参数就没必要建一个JavaBean所以
JSONObject rawObject = getRawObject();
Integer id = rawObject.getInteger("id");
- 对数字进行运算的时候可以参考Hutool里的NumberUtil.sub / div等等
- 调用另一个接口用
Ret priceRet = this.getPrice(vo, userId);
JSONObject priceJson = JSONUtil.parseObj(priceRet.get(DATA));
传过来的类型要把他解析json
priceJson.getBigDecimal(R.PAY)
- 定义枚举想通过枚举在HTML前端显示,那么就要转为List来进行返给前端进行遍历
List<AssistantEnum> assistantEnums = Arrays.asList(AssistantEnum.values());
set("assistantEnums",assistantEnums);
主要是通过asList转为集合,前端将list遍历然后通过name就可以获取到了
- 判断是否为空用StrUtil.hasBlank() , 这个既可以判断是否是null和" " 非常好用
- Validator.isEmail() 判断邮箱是否符合规则 格式验证
- IdUtil.simpleUUID(); 随机生成唯一ID
- String paswd = RandomUtil.randomString(6); 生成随机字符串 包含数字和字符串
- 产生随机数
String clientForgetPwdRandom RandomUtil.randomNumbers(VERIFY_CODE_LEN);
- 获取当前请求的域名
String linkHeader = HttpUtil.getRootUrl(getRequest());
- 发送邮件进行异步处理
Controller:
String linkHeader = HttpUtil.getRootUrl(getRequest());
ManageMailVo mailVo=new ManageMailVo(); //要想传值 规范性建vo 然后类型放进去
mailVo.setSessionAttr(sessionAttr);
mailVo.setVo(vo);
mailVo.setLinkHeader(linkHeader);
mailVo.setUserId(userId);
// 发送邮件
Jboot.sendEvent(new JbootEvent(Event.COMPLETE_MESSAGE_EMAIL,mailVo));
业务事件处理监听器(所有业务处理放在此监听)
@EventConfig(action = {Event.COMPLETE_MESSAGE_EMAIL }) //这里需要在Const里定义常量
...
}else if(event.getAction().equals(Event.COMPLETE_MESSAGE_EMAIL) ){
sendEmail(event);
/**
* 人员管理 发送邮件
* @param event
* @return
*/
public void sendEmail(JbootEvent event) {
ManageMailVo vo = event.getData();
String email = vo.getSessionAttr().getEmail();
String password = vo.getVo().getTemporaryPassword();
String linkHeader = vo.getLinkHeader();
Long userId = vo.getUserId();
SysMail smr =new SysMail();
SysUser firstEmail = sysUserService.findFirstByColumns(Columns.create(SysUser.C_EMAIL, email));
String payLink = StrUtil.format("{}/manage", linkHeader);
try {
//定位模板文件地址
String fileName = PathKit.getRootClassPath() + "/template/mail/mail_agent_regForUser.html";
//Engine.use().removeAllTemplateCache(); //调试时打开不会缓存模板内容可实时查看内容变化
//设置模板内容中所需参数的实际值
Kv params = Kv.by("email", email)
.set("password",password)
.set("payLink", payLink)
.set("now", DateUtil.now());
//调用渲染方法得到实际内容
String htmlContent = Engine.use().getTemplate(fileName).renderToString(params);
//调用邮件工具发送HTML格式的邮件
String sendResult = MailUtil.sendHtml(email, "补充信息", htmlContent);
log.info(StrUtil.format("作家登录链接发送成功:{} send mail = {}", payLink, email));
String clientEmail = sysUserService.findById(userId).getStr("email");
log.info(StrUtil.format("当前操作用户为:{}", clientEmail) );
smr.setTopic("请把信息补充完整并修改密码")
.setContent(StrUtil.format("跳转登录链接:{},用户邮箱:{}", payLink, email))
.setReceiveEmail(email)
.setReceiveUserId(firstEmail.getId())
.setSenderMail(clientEmail)
.setSenderUserId(userId.toString())
.setStatus(Const.emailType.UNSENT)
.setCreateTime(new Date())
.save();
Jboot.sendEvent(new JbootEvent(Event.SEND_MAIL, smr));
} catch (Exception e) {
log.error("添加用户邮箱发送失败!异常信息={}", e.getMessage());
}
}
- 遇到在rap接口上,这种多重嵌套的
代码示例:
List<ManageWriterVo> list = managerService.getWriterTable(pageIndex,pageSize,startDate,endDate,keyword,RoleEnum.WRITER.getId());
ApiPageResultVo<ManageWriterVo> vo = new ApiPageResultVo<>();
vo.setCount(list.size());
vo.setMsg("");
vo.setCode(0);
vo.setData(list);
renderJson(vo);
@Override
@SqlMethod(platform = PlatformEnum.MANAGE)
public List<ManageWriterVo> getWriterTable(Integer pageIndex,Integer pageSize,String startDate,String endDate,String keyword,Long roleId) {
Page<Record> paginate = Db.template(sqlKey(), Kv.by("roleId",roleId.toString()).set()....paginate(pageIndex, pageSize);
List<Record> recordList = paginate.getList();
List<ManageWriterVo> list=new ArrayList<>();
for (Record record:recordList){
ManageWriterVo vo=new ManageWriterVo();
vo.setId(record.get("user_id").toString());
.....
list.add(vo);
}
return list;
}
- 也可以直接返回
return Ret.ok(DATA, data).set(Const.PageConst.COUNT, data.size()).set(R.CODE, PageConst.CODE_OK).set(Const.PageConst.TOTAL, dataList.getTotalRow());
- 经常会遇到数据库判空处理,这里就遇到某个字段赋值 如果是字符串那么就可以用
StrUtil.EMPTY
- 常常我们进行数字比较的时候用compareTo进行比较 而对于某些数字或者字符串多个地方会用到就要考虑去找常量或者枚举 double类型用NumberUtil 进行计算 如果是BigDecimal那么就是另一种计算
- StrUtil.format 这是用来字符串拼接的
String innerTradeNo = StrUtil.format("{}{}", DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN), RandomUtil.randomStringUpper(5));
//生成支付码
- 在sql中遇到多个if语句用来判断,但是如果有一个不成立那么也会出现问题
用1=1
当两个if 都不成立的时候,或者仅有第一个if 成立的时候,SQL语句拼接就会出现错误。当我们的SQL语句加上where 1=1的时候,就不报错了 - 在数据库中用if判断的时候notBlank的时候 是判断的是字符串 那么数字就要转为string类型
- 在做设置进行验证码时候需要对这验证码进行时间限制
//把验证码放进redis里 进行时间限制 3分钟
JbootRedis redis = Jboot.getRedis();
redis.setex(VERIFY_REDIS,180,clientForgetPwdRandom);
//获取这个验证码
String code = redis.get(VERIFY_REDIS);
//清除redis里面的验证码 修改成功后应该清除这个验证码
JbootRedis redis = Jboot.getRedis();
redis.del(VERIFY_REDIS);
- 校验验证码是否合法
//校验密码是否合法
if (vo.getConfirmPwd().length() >= 6 && vo.getNewPwd().length() <= 16) {
// 数字出现的次数
Integer numCount = 0;
// 小写字母出现的次数
Integer lowerCount = 0;
// 大写字母出现的次数
Integer upperCount = 0;
//记录
char[] charArray = vo.getConfirmPwd().toCharArray();
for (int i = 0; i < charArray.length; i++) {
if (charArray[i] >= 'a' && charArray[i] <= 'z') {
lowerCount += 1;
} else if (charArray[i] >= 'A' && charArray[i] <= 'Z') {
upperCount += 1;
} else if (charArray[i] >= '0' && charArray[i] <= '9') {
numCount += 1;
}
}
if (numCount > 0 && lowerCount > 0 && upperCount > 0) {
renderJson(Ret.ok(MSG,i18nText("校验通过!")));
}else{
renderJson(Ret.fail(MSG,i18nText("密码不合法!密码至少六位,密码中要包含大小写字母!")));
return;
}
}else{
renderJson(Ret.fail(MSG,i18nText("密码最少6位,最长是16位!")));
return;
}
- 在修改密码后会跳转到登录页面,此时也要清除session里用户信息
//用户修改成功登出 清除session
if (ret.isOk()) {
userLogout();
}
- 一般数据库都对密码进行加盐处理 那么 在判断密码是否跟数据库密码是否一致
//对密码进行加盐处理
String salt = user.getSalt();
//如果获取到的盐为空
if (StrUtil.isBlank(salt)) {
salt = HashKit.generateSaltForSha256();
}
String saltPwd = HashKit.sha256(salt + password);
//新密码跟旧密码不能相同
if (saltPwd.equals(user.getPwd())) {
return Ret.fail(MSG, "新密码不能跟旧密码相同!");
}
- 使用postman
(1).使用from -data表单提交就在这,(旁边的from-data是用来提交文件的)
(2).模拟JSON数据 在raw中模拟前台传的格式信息
55.Date日期作比较用before可以直接比较,意思是:前者小于后者则返回true
turnoverTime.before(new Date())
- 集合List判断是否为空也是为0
CollUtil.isEmpty(appeals)
- 数据库select语句 都用别名 这样 即使后面查询字段不对那么也不影响数据的对应
- .获取前端参数时用vo接收,那么也对他进行null值判断
- 将vo发给mysql 这样就有多个参数传值
List<Record> list = Db.template(sqlKey(), Kv.by("stage", StageEnum.REFUND.getId()).set(VO, vo) ).find();
- 接收前端传来的fromData值 目前只接收一层
ManageApplyQueryVo vo = getFormVo(ManageApplyQueryVo.class);
- 接收前端传过来的JSON
public void refuseMeansReason(@JsonBody ManageAppendDenyVo vo) {
Ret ret = slOrderAppendService.denyAppend(vo, getUserId());
renderJson(ret);
}
- 接收时看数据表的字段类型
setOrderType( r.getStr("orderType") )
- 在数据库判空-开始和结束时间可以用这种规范
#if(vo.startDate)
and sor.apply_time >= #para(vo.startDate)
#end
#if(vo.endDate)
and sor.apply_time <= #para(vo.endDate)
#end
#if(vo.keyword)
and so.abstract like CONCAT('%',#para(vo.keyword),'%')
#end
- 能用链式用法就用链式用法
data.add(new ManageApplyResultVo()
.setOrderType( r.getStr("orderType") )
64.遇到在数据库写SQL语句时,遇到关于in的用法时
Controller:
List.of( OrderStatusEnum.已完成.getId(), OrderStatusEnum.待付款.getId(), OrderStatusEnum.进行中.getId() )
SQL:
and so.es_type_id in ( #inparas(orderTypeList) )
65.如果遇到画面字段太长 需要进行修改
item.setTitle(StrUtil.maxLength(order.getCourseName(), 10));
66.在进行日期对比的时候
long between = DateUtil.between(new Date(), order.getTurnoverTime(), DateUnit.DAY, false);
注明:判断两者之间相差天数,不考虑时间先后,为true
判断两者之间相差天数,考虑日期先后顺序,为false
1:在最后加一个参数Boolean值 参数的顺序很重要
a:int1:2020-11-1 int2:2020-12-1
a:小,大 false 31天 true:31天
b:大,小 false -31天 true:31天
67.在mysql中返回日期或日期时间表达式datetime_expr1 和datetime_expr2the 之间的整数差。
TIMESTAMPDIFF(HOUR, NOW(), turnover_time) BETWEEN 24 AND 0
判断:日期先后顺序比较重要, 小,大 为正
大,小 为负
68.mysql 判空 IFNULL
ifnull(so.page_num,1)
判空,如果第一个为空,那么取第二个值
69.可以传vo值 给mysql来进行判断
-- 判断服务种类 0:全部 1:作家订单 2: 润色订单 3:润色新订单
#if(vo.serviceType !=null && vo.serviceType != 0)
#if (vo.serviceType == 1) --作家
and (so.cate = 0 and so.stage >=1 and so.stage <=3)
#else if (vo.serviceType == 2) --润色
and so.cate = 1
#else if (vo.serviceType == 3) --润色新
and (so.cate = 0 and so.stage >= 4)
#end
#end
70.获取到时间需要格式化
item.setEndDate(DateUtil.formatDateTime(r.getDate("endDate")));
71.判断集合是否存在数值
Collutil.isnotEmpty()
72.写mysql获取全部订单,然后获取进行过滤
//获取所有的待分配订单-所有
List<SlOrder> orders = getAssignableOrders();
//过滤
for ( Iterator<SlOrder> it = orders.iterator(); it.hasNext(); ) {
SlOrder order = it.next();
//判断关键词过滤
if (StrKit.notBlank(vo.getKeyword())) {
if ( ! order.getAbstract().contains(vo.getKeyword()) && ! order.getInnerServiceType().contains(vo.getKeyword())
&& ! order.getSchoolRequire().contains(vo.getKeyword()) && !order.getRequirement().contains(vo.getKeyword()) ) {
it.remove();
continue;
}
}
73.遇到页面上需要对第一个第二个样式进行改变
<!-- 太平洋时间数据 -->
<div id="tpy" class="layui-hide">
#for(x:slDynamicList)
<div class="process
#if(for.first)
active
#end
#if(for.count==2)
current
#end">
<span class="time">#(x.time ??)</span>
<span>#(x.title ??)</span>
</div>
#end
</div>
- StrUtil.cleanBlank 会去除该字符串中所有空白
SpringBoot笔记
hutool 日期常量 DatePattern
分割符常量 StrUtil.COMMA
-
返回Boolean值 修改成功会返回int 然后>0 就可以
-
学习java排序
//排序后再分页
CollUtil.sort(orders, new Comparator<SlOrder>() {
@Override
public int compare(SlOrder o1, SlOrder o2) {
return -o1.getCreateTime().compareTo(o2.getCreateTime());
}
});
List<SlOrder> orderList = new ArrayList<>();
if (CollUtil.isNotEmpty(orders)) {
List<SlOrder> subList = CollUtil.page(vo.getPageIndex(), vo.getPageSize(), orders);
if (CollUtil.isNotEmpty(subList)) {
orderList = subList;
}
}
-
判断字符串不为空
isBlank() -
判断集合没有元素
isEmpty() -
连接字符串 (原生的)
xx.concat("") -
比较时间dateTime
select * from t1 where unix_timestamp(time1) > unix_timestamp('2011-03-03 17:39:05') and unix_timestamp(time1) < unix_
timestamp('2011-03-03 17:39:52');
就是用unix_timestamp函数,将字符型的时间,转成unix时间戳。个人觉得这样比较更踏实点儿。
- springBoot事务
@Transactional(rollbackFor = Exception.class)
- 索引 对连接字段进行索引, 当检索条件比较慢的时候就用索引,对需要被连接的那个字段进行索引会快很多。
- 写SQL时 如果在navicat里写好了在复制到代码中,其中status就会大写,这样如果代码发行到linux的话就会区分大小写从而找不到,所以应该
status
标注好 - 日期比较用after和before 然后通俗的话就是after 就是 > before就是<
- lambda表达式对list里的进行循环累加
Integer total = data.stream().mapToInt(EventDomainListCountVo::getNum).sum();
- lombok注解中使用这个注解 可以达到链式编程的目的
@Accessors(chain = true)
- 对与请求AI的接口 进出都需要打印日志
- 查询SQL中不能直接查询配置表的数据 应先查询 如果不存在则需要判断
- 写SQL种大于跟后面等于 不能有空格 会报错
- lambda表达式进行排序
List<SensitivesListVo> sortCollect = voList.stream().sorted(Comparator.comparing(SensitivesListVo::getStatus)).collect(Collectors.toList());
endsWith
是否以指定后缀结束BeanUtil.isEmpty
判断该对象中的属性是否都为空。可以经常在业务中判断查询条件是否有数据。但是这个判断的是该对象中的全部属性。无法针对单个属性并且这属于hutool工具类的。- 获取某个文件的路径
private static final String WORD_BASE_PATH = "word-template";
.....
String templatePath = Paths.get(SystemUtils.USER_DIR, WORD_BASE_PATH) + File.separator + templateName;
// 其中templateName是模板名称,File.separator 是斜杠,这里就能获取到该项目下的文件路径
- 常见业务命名 :
如果1是有效:STATUS_VALID
0无效 :STATUS_INVALID
-1删除:STATUS_DEL
- 以工作流的方式下载文件
//这里path是得到文件的路径地址
String path = getRealPath(templateId, fileName, object);
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
//这里download是调用的是另一个方法,是下载的,去看bzzn项目中com/bzzn/stability/service/impl/WordTemplateServiceImpl.java:62
FileUtils.downLoad(path, response, false);
- 下载
public static void downLoad(String filePath, HttpServletResponse response, boolean isOnLine) throws IOException {
String realPath = System.getProperty("user.dir") + filePath;
File f = new File(realPath);
if (!f.exists()) {
response.sendError(404, "File not found!");
return;
}
try(BufferedInputStream br = new BufferedInputStream(new FileInputStream(f))) {
byte[] buf = new byte[1024];
int len = 0;
response.reset(); // 非常重要
if (isOnLine) { // 在线打开方式
URL u = new URL("file:///" + realPath);
response.setCharacterEncoding("utf-8");
response.setContentType(u.openConnection().getContentType());
response.setHeader("Content-Disposition", "inline; filename=" + java.net.URLEncoder.encode(f.getName(), "UTF-8"));
// 文件名应该编码成UTF-8
} else { // 纯下载方式
response.setContentType("application/x-msdownload");
response.setCharacterEncoding("utf-8");
response.setHeader("Content-Disposition", "attachment; filename=" + java.net.URLEncoder.encode(f.getName(), "UTF-8"));
}
OutputStream out = response.getOutputStream();
while ((len = br.read(buf)) > 0) {
out.write(buf, 0, len);
}
out.close();
}
}
其中:
System.getProperty
(“user.dir”) :获取程序当前路径
- 针对html中进行爬虫 获取其中内容进行修改
获取本机的文件进行读取
@Test
public void t1() {
final String BASE64_IDENTITY = "base64,";
String html = FileUtil.readString("D:\\zzz\\test.html", "utf-8");
Document doc = Jsoup.parse(html);
Elements imgTags = doc.select("img[src]");
System.out.println("=====imgsTag====" + imgTags);
for (Element element : imgTags) {
// String src = element.attr("abs:src");// 获取src的绝对路径
String srcBase64 = element.attr("src");// 获取src的绝对路径
// System.out.println("===src===" + src);
System.out.println("===src2===" + srcBase64);
//srcBase64 = srcBase64.replace("data:image/jpeg;base64,", "");
int idx = StrUtil.indexOfIgnoreCase(srcBase64, BASE64_IDENTITY, 0);
if ( idx > 0 ) {
String base64Prefix = StringUtils.left(srcBase64, idx + StrUtil.length(BASE64_IDENTITY));
String trueContent = StrUtil.replace(srcBase64, base64Prefix, StrUtil.EMPTY).strip();
//将文件保存为本地文件后
String suffix = ".png";
if (suffix.toLowerCase().contains("jpeg") ||suffix.toLowerCase().contains("jpg")) {
suffix = ".jpg";
}
File saveDir = new File("d:/");
File tempFile = FileUtil.createTempFile("knowledge_", suffix, saveDir, true);
Base64.decodeToFile(trueContent, tempFile);
//将静态文件访问路径替换原来的base64表示的img节点的src属性
String url = "http://localhost:8080/" + "/" + tempFile.getName();
element.attr("src", url);
}
}
FileUtil.writeString(doc.html(), new File("2.html"), "utf-8");
}
}
下面是正式代码,上面是例子
/**
* 将html中包含有base64表示的图像,变为可用http请求访问的url地址(以便前端 fileSaver能够保存)
*/
public static String img2Url(String html,String requestUrl) {
try {
final String BASE64_IDENTITY = "base64,";
// html = html.toLowerCase(); 转小写后,图片转码会显示不出
//解析HTML
Document doc = Jsoup.parse(html);
Elements imgTags = doc.select("img[src]");
for (Element img : imgTags) {
// 获取src的绝对路径
// String src = img.attr("abs:src");
String srcBase64 = img.attr("src");// 获取src的绝对路径
int idx = StrUtil.indexOfIgnoreCase(srcBase64, BASE64_IDENTITY, 0);
if (idx > 0) {
String base64Prefix = StrUtil.subPre(srcBase64, idx + StrUtil.length(BASE64_IDENTITY));
String content = StrUtil.replace(srcBase64, base64Prefix, StrUtil.EMPTY).trim();
//将文件保存为本地文件
String suffix = ".png";
if (base64Prefix.toLowerCase().contains("jpeg") || base64Prefix.toLowerCase().contains("jpg")) {
suffix = ".jpg";
}
File tempFile = FileUtil.createTempFile("auxiliary_knowledge_", suffix, new File(basePath), true);
Base64.decodeToFile(content, tempFile);
//将静态文件访问路径替换原来的base64表示的img节点的src属性,此处要确认能够被前端访问(可以写一个请求方法,或者走下载接口)
String url = requestUrl + "/common/auxiliary/img?path=" + tempFile.getName();
img.attr("src", url);
}
}
return doc.html();
} catch (Exception e) {
e.printStackTrace();
}
return html;
}
重点方法说明:
获取路径:
request.getRequestURL()
: 获取完整路径 如:http://localhost:8080/bzbs/system/login.jsp
request.getServletPath()
:返回除去host和工程名部分的路径 如/system/login.jsp
request.getRequestURI()
:返回除去host(域名或者ip)部分的路径 如/bzbs/system/login.jsp
request.getContextPath()
:返回工程名部分,如果工程映射为/,此处返回则为空 如 /bzbs
字符串StrUtil:
StrUtil.indexOfIgnoreCase
(srcBase64, BASE64_IDENTITY, 0); 查找指定位置,没有返回-1
hutool工具:
File tempFile = FileUtil.createTempFile
(“auxiliary_knowledge_”, suffix, new File(basePath), true);
创建一个临时文件
Base64.decodeToFile
(content, tempFile); base64转码到指定文件
25.在做文件预览的时候有一个踩坑,我现在将它记录下来:
kkview文件预览的时候,如果直接返回路径(xxx)是不会显示的,在调用源码的过程中发现要正常实现文件预览那么首先得到一个流,这个流要从下载的二进制流才能正常识别,并且后缀还得加上fullfilename文件名才能正常实现。
1.这是最开始预览文件路径:
http://172.19.2.109:8012/onlinePreview?officePreviewType=pdf&url=aHR0cDovLzE3Mi4xOS4yLjEwOTo3Nzc3L3N0YWJpbGl0eS8vYXV4aWxpYXJ5L3VwbG9hZC%2FooYzkuJrmlrnmoYhfMjAyMTA1MTMxODEwMzYuZG9j
2.对后面的base64进行解码
http://172.19.2.109:7777/stability//auxiliary/upload/行业方案_20210513181036.doc
3.解决:
路径改成
http://192.168.12.48:8888/stability/common/auxiliary/download/?path=/auxiliary/upload/xx.docx&fullfilename=x.docx
4.你可以写死common/auxiliary/download/?path= 也可以调用下载接口 然后再拼路径
26.上传接口
@ApiOperation("上传附件")
@PostMapping("/auxiliary/upload")
public Result<Map<String, String>> fileUpload(MultipartFile file) throws IOException {
PathUtil.mkdir(UPLOAD_BASE_PATH);
String originalFilename = FileNameCleaner.cleanFileName(file.getOriginalFilename());
String newName = FileNameUtil.mainName(originalFilename) + "_" + DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN) + "." + FileNameUtil.extName(file.getOriginalFilename());
Path targetPath = UPLOAD_BASE_PATH.resolve(newName);
file.transferTo(targetPath);
Map<String, String> map = new HashMap<>();
map.put("name", originalFilename);
map.put("path", "/auxiliary/upload/" + newName);
return ResultGenerator.genSuccessResult(map);
}
27:解析apk安装包:
解析apk安装包用ApkFile
//解析apk
try(var apkFile = new ApkFile(file)) {
//Apk的元信息
var apkMeta = apkFile.getApkMeta();
//如果前端不传apk名字则自己获取
if (isBlank(dto.getName())) {
dto.setName(apkMeta.getName());
}
//apk版本名字和code
String versionName = apkMeta.getVersionName();
Long versionCode = apkMeta.getVersionCode();
//获取图标
List<IconFace> icons = apkFile.getAllIcons();
var iconId = "";
if (CollUtil.isNotEmpty(icons)) {
byte[] bytes = icons.get(icons.size() - 1).getData();
String encode64 = Base64.encode(bytes);
//上传图标文件
iconId = uploadImg(encode64);
}
28:解析html用jsoup
之前业务场景中是维稳中前端是从富文本传过来是个html然后我们需要解析
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.13.1</version>
</dependency>
//业务代码:
private String getDocPlainText(RemindLetterFileDetailVo deptVo) {
try {
List<String> skipWords = CollUtil.newArrayList("Document");
Document doc = Jsoup.parse(deptVo.getHtmlTpl());
String text = doc.text();
for (String w : skipWords) {
text = StringUtils.removeStart(text, w);
}
return text;
} catch (Exception e) {
log.error("将富文本转为纯文本时异常=" + e.getMessage());
}
return EMPTY;
}
29:websocket使用:
<!-- websocket dependency -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
//现在表述不清楚,需要的时候看维稳项目看
30:一般在请求http的时候都要打印耗时时长,这是用于排查问题
//开始
TimeInterval timer = DateUtil.timer();
//结束
timer.intervalPretty()