work笔记

work笔记

一、mysql函数

创建用户并且授权

CREATE USER 'testroot'@'%' IDENTIFIED BY 'testpassword';
GRANT ALL ON test.* TO 'testroot'@'%';

count

查询status为1,2的数量

select 
    count(case when status = '1' then 1 end) as count1,
	count(case when status = '2' then 1 end) as count2
from tb 

group_concat

group_concat函数常用于select 语句中,下面我们通过一张表来讲解group_concat函数的用法。

以下为表exam内容

|id    |subject |student|teacher|score|
---------------------------------------
|1	   |数学    |小红	|王老师	|80   |
|2	   |数学    |小李	|王老师	|80   |
|3	   |数学    |小王	|王老师	|70   |
|4	   |数学    |小张	|王老师	|90   |
|5	   |数学    |小赵	|王老师	|70   |
|6	   |数学    |小孙	|王老师	|80   |
|7	   |数学    |小钱	|王老师	|90   |
|8	   |数学    |小高	|王老师	|70   |
|9	   |数学    |小秦	|王老师	|80   |
|10	   |数学    |小马	|王老师	|90   |
|11	   |数学    |小朱	|王老师	|90   |
|12	   |语文    |小高	|李老师	|70   |
|15	   |语文    |小秦	|李老师	|70   |
|18	   |语文    |小马	|李老师	|80   |
|21	   |语文    |小朱	|李老师	|90   |
|24	   |语文    |小钱	|李老师	|90   |

我们希望按分数score进行分组,并查看分组后的学生姓名,就可以用group_concat实现。执行sql:

select score,group_concat(student) from exam group by score;

执行结果为:

|score |group_concat(student)		|
-------------------------------------
|70	   |小王,小赵,小高,小高,小秦		|
|80	   |小红,小李,小孙,小秦,小马		|
|90	   |小张,小钱,小马,小朱,小朱,小钱  |

在70分这一行有两条小高的记录,90分这一行有两条小钱和小朱的记录,如果我们需要去重,则需要给函数中加一个distinct参数:

select score,group_concat(distinct student) from exam group by score;

执行结果为:

|score |group_concat(student)	|
---------------------------------
|70	   |小王,小赵,小高,小秦		|
|80	   |小红,小李,小孙,小秦,小马	|
|90	   |小张,小钱,小马,小朱		|

这样group_concat每行数据的结果中就没有了重复值,但是在数据中的分隔符为默认的逗号’,',如果想修改默认的分隔符,只需要在上述指令中稍作修改:

select score,group_concat(distinct student separator '%') from exam group by score;

执行结果:

|score |group_concat(student)	|
---------------------------------
|70	   |小王%小秦%小赵%小高		|
|80	   |小孙%小李%小秦%小红%小马	|
|90	   |小张%小朱%小钱%小马

any_value

1.MySQL5.7之后,sql_mode中ONLY_FULL_GROUP_BY模式默认设置为打开状态。

2.ONLY_FULL_GROUP_BY的语义就是确定select target list中的所有列的值都是明确语义,简单的说来,在此模式下,target list中的值要么是来自于聚合函数(sum、avg、max等)的结果,要么是来自于group by list中的表达式的值

3.MySQL提供了any_value()函数来抑制ONLY_FULL_GROUP_BY值被拒绝

4.any_value()会选择被分到同一组的数据里第一条数据的指定列值作为返回数据
例子:

student_score表:

+----+-----------+--------+-------+-----------------+
| id | username  | course | score | class           |
+----+-----------+--------+-------+-----------------+
|  1 | 小王    | 英语 |    15 | 三年级一班 |
|  2 | 小李    | 英语 |    10 | 三年级一班 |
|  3 | 小艾    | 数学 |     5 | 三年级一班 |
|  4 | 小星    | 数学 |    52 | 三年级二班 |
|  5 | 蔡国    | 数学 |   100 | 三年级二班 |
|  6 | 诸葛里 | 英语 |    89 | 三年级二班 |
|  7 | 东方曜 | 数学 |   100 | 三年级二班 |
|  8 | 小赵    | 数学 |   100 | 三年级一班 |
+----+-----------+--------+-------+-----------------+

查询

SELECT class,any_value(username)
FROM student_score
group by class;

在这里插入图片描述

正确查询

SELECT class,any_value(username)
FROM student_score
group by class;

查询结果

+-----------------+---------------------+
| class           | any_value(username) |
+-----------------+---------------------+
| 三年级一班 | 小王              |
| 三年级二班 | 小星              |
+-----------------+---------------------+

concat/concat_ws-将多个字符串连接成为一个字符串

concat返回结果为连接参数产生的字符串,如果有任何一个参数为null,则返回值为null。

以sudent_score表为例,concat_ws进行分

select concat(id,name) as string,concat_ws(',',id,name) as string2
from student_score;

查询结果

+--------+---------+
| string | string2 |
+--------+---------+
| 1li    | 1,li     |
| 2li    | 2,li     |
| 3x     | 3,x      |
+--------+---------+

case-类似于switch case

用法

SELECT <myColumnSpec>
     CASE
         WHEN <A> THEN <somethingA>
         WHEN <B> THEN <somethingB>
         ELSE <somethingE>
     END 
FROM <tableName>

例子:

SELECT
     Title,
    'Price Range' =
    CASE
        WHEN price IS NULL THEN 'Unpriced'
        WHEN price < 10 THEN 'Bargain'
        WHEN price BETWEEN 10 and 20 THEN 'Average'
        ELSE 'Gift to impress relatives'
    END
FROM titles
ORDER BY price

json_unquote与json_extract

json_unquote 去除json字符串的引号,将值转成string类型

json_extract截取数据库中指定字段中存储的json数据中的某个字段对应的值

使用例子

-- 表 tb_json, json字段 content
select json_unquote(json_extract( tj.content, '$.list' ))
from tb_json tj;

查询5分钟内的数据

select * from tb_order
where is_deleted = 0
and create_time <= date_sub(now(),interval 5 minute)

查询本月数据

select count(*) from tb_parking_lot_data where parking_name = '十四都游客服务中心' and date_format( create_time, '%Y-%m') = date_format('2022-09-18 12:44:34', '%Y-%m') and total = used and status =1 and is_deleted=0;

select count(*) from tb_parking_lot_data 
where date_format( create_time, '%Y-%m') = date_format(now(), '%Y-%m')
and status =1 and is_deleted=0;

查询上月数据

select count(*) from tb_parking_lot_data 
where period_diff(date_format(now(),'%Y-%m' ),date_format( create_time,'%Y-%m')) =1
and status =1 and is_deleted=0;

查询每天每小时的数据

select hour(create_time) as hour , count(*) as count
from tb_parking_lot_data 
where create_time >'2022-10-18' and create_time<'2022-10-18 239:59'
group by hour(create_time);

查询最近七天每天的数据

select DATE_FORMAT( "update_time", '%Y-%m-%d' ) days,count(*) count
from( 
    select * from tb
    WHERE DATE_SUB( CURDATE( ), INTERVAL 7 DAY ) <= date( "update_time") ) as tb
GROUP BY
days;

查询一周内数据(周一到周日)

select * from tb_pairing_support_log tpsl
where is_deleted = 0
and yearweek(date_format(create_time,'%Y-%m-%D'),1) = yearweek(date_format(now(),'%Y-%m-%D'),1);

查询重复数据

-- 查询手机号,姓名, 角色重复的信息
select * from blade_user b
where (b.account,b.real_name,b.role_id) in (
   select account,real_name,role_id from blade_user
	 where is_deleted = 0
	 group by account,real_name,role_id having count(*) > 1
)

String TO Date

-- start_time_str: 20221010 => 2022-10-10
update tb_waicao_workshop
set start_time = str_to_date(start_time_str,'%Y%m%d') 
where start_time_str is not null;

根据身份证号设置省份

update tb_screen_rent_people ts
set ts.province = (
  select
  case left(ts.id_number,2)
  when '11' then '北京市'
  when '12' then '天津市'
  when '13' then '河北省'
  when '14' then '山西省'
  when '15' then '内蒙古自治区'
  when '21' then '辽宁省'
  when '22' then '吉林省'
  when '23' then '黑龙江省'
  when '31' then '上海市'
  when '32' then '江苏省'
  when '33' then '浙江省'
  when '34' then '安徽省'
  when '35' then '福建省'
  when '36' then '江西省'
  when '37' then '山东省'
  when '41' then '河南省'
  when '42' then '湖北省'
  when '43' then '湖南省'
  when '44' then '广东省'
  when '45' then '广西壮族自治区'
  when '46' then '海南省'
  when '50' then '重庆市'
  when '51' then '四川省'
  when '52' then '贵州省'
  when '53' then '云南省'
  when '54' then '西藏自治区'
  when '61' then '陕西省'
  when '62' then '甘肃省'
  when '63' then '青海省'
  when '64' then '宁夏回族自治区'
  when '65' then '新疆维吾尔自治区'
  when '71' then '台湾省'
  when '81' then '香港特别行政区'
  when '82' then '澳门特别行政区'
  else '未知'
  end
)
where ts.id_number is not null;

二、mybatis标签

XMl 大于小于

大于等于

<![CDATA[ >= ]]>

小于等于

<![CDATA[ <= ]]>

like

<if test="accountInfo.villageName!=null and accountInfo.villageName!=''">
    and village_name like concat(concat('%', #{accountInfo.villageName}),'%')
</if>

where

where标签的作用类似于动态sql中的set标签,主要用来简化sql语句中where条件,使得MyBatis 自动判断是否需要加where语句,并且避免在查询条件开头强制添加类似WHERE state = 'ACTIVE'语句之后,才可以添加if标签。

使用案例如下所示:

   <select id="selectByParams" parameterType="map" resultType="user">
    select * from user
    <where>
      <if test="id != null ">and id=#{id}</if>
      <if test="name != null and name.length()>0" >and name=#{name}</if>
      <if test="gender != null and gender.length()>0">and gender = #{gender}</if>
    </where>
   </select>

案例解析:按照标准写法,第一个标签内应该不写 and,但是,即便写了and也不会报错,因为where标签自动地帮助我们移除了第一个and关键字。温馨提示,第二个之后的标签内,必须有 and 关键字。

where标签只有在一个以上的if条件有值的情况下,才去主动添加WHERE子句。而且,若紧邻where关键字的内容是“AND”或“OR”开头,则where标签自动把它们去除。例如,在上述SQL中,如果只有查询条件id的值为null,那么控制台打印出来的SQL为:

select * from user where name="xx" and gender="yy";

if

如果条件成立就附加之间的sql语句,如果条件不成立就不附加之间的sql语句。

foreach

示例

public List<User> queryUserByIdsList(List<Integer> list);
        SELECT project_name FROM project WHERE project_id IN
        <foreach collection="list" index="index" item="item" open="(" close=")" separator=",">
            #{item}
        </foreach>

在这里插入图片描述

三、JAVA/SpringBoot

获取从今天起(包括今天)的七天日期

List<String> dateList = new ArrayList<>();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
for (int i = 0; i < 7; i++) {
    Date date = DateUtils.addDays(new Date(), i);
    String formatDate = sdf.format(date);
    dateList.add(formatDate);
}

获取年月日

Date birthday = one.getBirthday();
Calendar calendar = Calendar.getInstance();
calendar.setTime(birthday);
// 出生年份
int birthDayYear = calendar.get(Calendar.YEAR);

java8-stream流

链接:https://www.runoob.com/java/java8-streams.html

遍历
personList.stream().forEach(person -> {
    if(person.getAge() > 11){
        person.setId(0L);
    }
});
排序-sorted
// 默认升序, reversed() 倒序 
 List<Person> sortList = personList.stream().limit(1).sorted(Comparator.comparing(Person::getAge).reversed()).collect(Collectors.toList());
过滤-filter
// 过滤数据集-获取集合中年龄小于11岁的数据
List<Person> personList2 = personList.stream().filter(person -> person.getAge() < 11).collect(Collectors.toList());
获取指定数量-limit
// 获取排序后的前两个数据
List<Person> sortList = personList.stream().sorted(Comparator.comparing(Person::getAge).reversed()).limit(2).collect(Collectors.toList());
        System.out.println(sortList);

XXXWrapper-返回视图层所需的字段

例子:

/**
 * 包装类,返回视图层所需的字段
 *
 * @author Chill
 */
public class DeptWrapper extends BaseEntityWrapper<Dept, DeptVO> {

	private static IDeptService deptService;

	static {
		deptService = SpringUtil.getBean(IDeptService.class);
	}

	public static DeptWrapper build() {
		return new DeptWrapper();
	}

    /**
      * 单个dept -> deptVo
      */
	@Override
	public DeptVO entityVO(Dept dept) {
		DeptVO deptVO = BeanUtil.copy(dept, DeptVO.class);
		if (Func.equals(dept.getParentId(), CommonConstant.TOP_PARENT_ID)) {
			deptVO.setParentName(CommonConstant.TOP_PARENT_NAME);
		} else {
			Dept parent = deptService.getById(dept.getParentId());
			deptVO.setParentName(parent.getDeptName());
		}
		return deptVO;
	}

    /**
      * 多个dept -> 多个deptVo
      */
	public List<DeptVO> listNodeVO(List<Dept> list) {
		List<DeptVO> collect = list.stream().map(dept -> BeanUtil.copy(dept, DeptVO.class)).collect(Collectors.toList());
		return ForestNodeMerger.merge(collect);
	}
    
    /**
      * 多个dept -> 多个deptVo
      */
    public List<INode> listNodeVO(List<Dept> list) {
		List<INode> collect = list.stream().map(this::entityVO).collect(Collectors.toList());
		return ForestNodeMerger.merge(collect);
	}

}

在视图层进行转换

1.单个情况

   /**
	 * 详情
	 */
	@GetMapping("/detail")
	@ApiOperationSupport(order = 1)
	@ApiOperation(value = "详情", notes = "传入role")
	public R<RoleVO> detail(Role role) {
		Role detail = roleService.getOne(Condition.getQueryWrapper(role),false);
		return R.data(RoleWrapper.build().entityVO(detail));
	}

2.多个情况

   /**
	 * 详情
	 */
	@GetMapping("/detail")
	@ApiOperationSupport(order = 1)
	@ApiOperation(value = "详情", notes = "传入role")
	public R<RoleVO> detail(Role role) {
		Role detail = roleService.getOne(Condition.getQueryWrapper(role),false);
		return R.data(RoleWrapper.build().entityVO(detail));
	}

3.分页情况-XXXWrapper.build().pageVO(pages)

   /**
     * 分页 党建活动详情
     */
    @GetMapping("/list")
    @ApiOperationSupport(order = 2)
    @ApiOperation(value = "分页 党建活动列表", notes = "传入partyActivityInfo")
    public R<IPage<ActivityInfoVO>> list(ActivityInfo partyActivityInfo, Query query) {
        IPage<ActivityInfo> pages = partyActivityInfoService.page(Condition.getPage(query), Condition.getQueryWrapper(partyActivityInfo));
        return R.data(PartyActivityInfoWrapper.build().pageVO(pages));
    }

增删改查

增/改

Controller

    /**
	 * 新增或修改
	 */
	@PostMapping("/submit")
	@ApiOperationSupport(order = 4)
	@ApiOperation(value = "新增或修改", notes = "传入dept")
	public R submit(@Valid @RequestBody Dept dept) {
        if(dept == null){
            throw new ServiceException("部门信息不能为空");
        }
        // 如果需要一些其他步骤,这样做,否则直接:deptService.saveOrUpdate(dept)
		return R.status(deptService.submit(dept));
	}

ServerImpl

    @Override
	public boolean submit(Dept dept) {
		CacheUtil.clear(CacheUtil.SYS_CACHE);
		// 检验**
		return saveOrUpdate(dept);
	}
根据多个id删除

Controller

    /**
	 * 删除 岗位表
	 */
	@PostMapping("/remove")
	@ApiOperationSupport(order = 7)
	@ApiOperation(value = "逻辑删除", notes = "传入ids")
	public R remove(@ApiParam(value = "主键集合", required = true) @RequestParam String ids) {
         // 如果需要一些其他步骤,这样做,否则直接:return R.status(postService.deleteLogic(Func.toLongList(ids)));
		return R.status(deptService.removeActivityInfo(ids));
	}

ServerImpl

@Override
    public boolean removeActivityInfo(String ids) {
        // 检验ids非空
        if (!StringUtil.isBlank(ids)) {
            // 检验***
        }
        return this.deleteLogic(Func.toLongList(ids));
    }
查详情
   /**
 	 * 1.根据id查询详情
	 */
	@GetMapping("/detail")
	@ApiOperationSupport(order = 1)
	@ApiOperation(value = "详情", notes = "传入region")
	public R<RegionVO> detail(@valid Region region) {
        if(Func.isEmpty(activityInfo) || null == region.getId()){
            throw new ServiceException("id不能为空");
        }
		Region detail = regionService.getById(region.getId());
		return R.data(RegionWrapper.build().entityVO(detail));
	}   

   /**
	 * 1.根据传参查询详情
	 */
	@GetMapping("/detail")
	@ApiOperationSupport(order = 1)
	@ApiOperation(value = "详情", notes = "传入region")
	public R<RegionVO> detail(@valid Region region) {
        if(Func.isEmpty(activityInfo)){
            throw new ServiceException("传入参数不能为空");
        }
		Region detail = regionService.getOne(Condition.getQueryWrapper(region));
		return R.data(RegionWrapper.build().entityVO(detail));
	}
分页查询
根据传入类型的具体非空参数进行查询
   /**
     * 分页-活动列表
     */
    @GetMapping("/list")
    @ApiOperationSupport(order = 5)
    @ApiOperation(value = "活动列表", notes = "传入activityInfo")
    public R<IPage<ActivityInfo>> list(Query query,ActivityInfo activityInfo) {
        IPage<ActivityInfo> page = activityInfoService.page(Condition.getPage(query), Condition.getQueryWrapper(activityInfo));
        return R.data(page);

    }
根据名称模糊查询,分页
IPage<ActivityInfo> page = activityInfoService.page(Condition.getPage(query), Wrappers.lambdaQuery(ActivityInfo.class).like(ActivityInfo::getName,"123"));
        return R.data(page);
根据名称模糊查询,按照时间排序,分页
IPage<ActivityInfo> page = activityInfoService.page(Condition.getPage(query), Wrappers.lambdaQuery(ActivityInfo.class).like(ActivityInfo::getName,"123").orderByDesc(ActivityInfo::getActivityStartTime));
        return R.data(page);
自定义分页查询

controller

   /**
	 * 自定义分页
	 */
	@GetMapping("/page")
	@ApiOperation(value = "分页", notes = "传入tenant")
	public R<IPage<Tenant>> page(Tenant tenant, Query query) {
		IPage<Tenant> pages = tenantService.selectTenantPage(Condition.getPage(query), tenant);
		return R.data(pages);
	}

ServiceImpl

    @Override
	public IPage<Tenant> selectTenantPage(IPage<Tenant> page, Tenant tenant) {
		return page.setRecords(baseMapper.selectTenantPage(page, tenant));
	}

Mapper

List<Tenant> selectTenantPage(IPage page,@Param("tenant")  Tenant tenant);

Mapper.xml

<select id="selectTenantPage" resultMap="tenantResultMap">
        select * from blade_tenant where is_deleted = 0;
    <if>
    </if>
</select>

防止用户多次点击

     String key = "Publish:"+ + AuthUtil.getUserId();
        Long increment = redisTemplate.opsForValue().increment(key, 1L);
        redisTemplate.expire(key, 2, TimeUnit.SECONDS);
        if (increment > 1L) {
            throw new ServiceException("请勿点击过快");
        }

通过图片URL下载图片到本地

package com.spring.cache.utils;

import org.springframework.stereotype.Component;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.UUID;

@Component
public class FileUtils {

    private final static String serverPath = "/sqt-workspace/images/";


    /**
     * 通过URL将图片存入本地
     * @param pictureUrl
     * @return
     */
    public static String getFile(String pictureUrl) throws IOException {

        URL url = new URL(pictureUrl);
        HttpURLConnection connection = (HttpURLConnection)url.openConnection();
        //设置请求方式
        connection.setRequestMethod("GET");
        //设置超时时间
        connection.setConnectTimeout(10*1000);
        //输入流
        InputStream stream = null;
        //输出流,图片输出的目的文件
        BufferedOutputStream fos = null;
        String pictureHostUrl = null;
        try {
            stream = connection.getInputStream();
            int len = 0;
            byte[] test = new byte[1024];

            //如果没有文件夹则创建
            File file = new File(serverPath);
            if (!file.exists()){
                file.mkdirs();
            }

            //设置图片名称
            String fileName = UUID.randomUUID() + ".png";
            pictureHostUrl = serverPath + fileName;

            fos = new BufferedOutputStream(new FileOutputStream(pictureHostUrl));
            //以流的方式上传
            while ((len = stream.read(test)) !=-1){
                fos.write(test,0,len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //关闭流,不然消耗资源
            if(stream != null){
                stream.close();
            }
            if(fos != null){
                fos.close();
            }
        }
        return pictureHostUrl;
    }

    public static boolean deletedFile(String pictureHostUrl) throws IOException {
        File file = new File(pictureHostUrl);
        if (file.exists()){
            return file.delete();
        }
        return true;
    }

//    public static void main(String[] args) throws IOException {
//        String pictureUrl = "https://meiyun-mng.oss-cn-hangzhou.aliyuncs.com/images/C76AA016-2604-4745-858F-DF4421366A39.png";
//        String filePath = FileUtils.getFile(pictureUrl);
//        System.out.println(filePath);
//        boolean b = FileUtils.deletedFile(filePath);
//        System.out.println(b);
//    }
}

配置类获取yaml配置

@Data
@Component
@ConfigurationProperties(prefix = "spring.redis")
public class RedissonProperties {

    private String host;

    private String port;

    private String password;

    private MySentinelProperties sentinel;

    private Integer database;

}

MQTT

1. 导入依赖
        <dependency>
			<groupId>org.eclipse.paho</groupId>
			<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
			<version>1.1.0</version>
		</dependency>
2. 编写回调类
@Component
@Slf4j
public class DaLingMqtt implements MqttCallback {
    @Autowired
    private ComponentService componentService;

    //地址端口
    private static final String HOST = "tcp://113.214.34.43:1883";
    //订阅的主题信息
    private static final String TOPIC = "root/cloxtec/dev_send/team_0";
    //用户名
    public static final String NAME = "";
    //密码
    public static final String PASSWORD = "";
    //生成当前客户端id
    public String CLIENTID = String.valueOf(System.currentTimeMillis());

    private MqttClient client;
    private MqttConnectOptions options;

//    @PostConstruct
    public void result() {
        try {
            log.info("DaLingMqtt 准备连接");
            //保存连接MQTT的客户端id  默认内存保存
            client = new MqttClient(HOST, CLIENTID, new MemoryPersistence());
            //MQtt连接设置
            options = new MqttConnectOptions();
            // 设置是否清空Session true设置为true表示每次连接到服务器都以新的身份连接
            options.setCleanSession(true);
            // 设置超时时间 单位为秒
            options.setConnectionTimeout(10);
            //设置心跳间隔来判断客户端是否在线
            options.setKeepAliveInterval(60);
            options.setAutomaticReconnect(false);
            // 设置回调
            client.setCallback(this);
            client.connect(options);
            //开始订阅消息
            //消息级别
            int Qos = 1;
            client.subscribe(TOPIC, Qos);
            log.info("DaLingMqtt 连接成功");
        } catch (Exception e) {
            log.info("ReportMqtt客户端连接异常,异常信息:" + e);
        }

    }

    @Override
    public void connectionLost(Throwable throwable) {
        try {
            log.info("程序出现异常,DReportMqtt断线!正在重新连接...:");
            client.close();
            this.result();
            log.info("ReportMqtt重新连接成功");
        } catch (MqttException e) {
            log.info(e.getMessage());
        }
    }

    @Override
    public void messageArrived(String topic, MqttMessage message) {
        log.info("获取接收消息主题:" + topic);
        log.info("获取接收消息级别Qos:" + message.getQos());
        String payload = new String(message.getPayload());
        log.info("获取接收的MQTT消息:>>>>>"+payload);
        //进行数据格式转换
        JSONObject json = JSONObject.parseObject(payload);
    }

    @Override
    public void deliveryComplete(IMqttDeliveryToken token) {
        log.info("消息发送成功");
    } 
}
3. 服务启动时连接
@SpringCloudApplication
@ServletComponentScan
@EnableBladeFeign
@EnableFeignClients(basePackages = "cn.beadata")
@EnableCaching
@MapperScan({"cn.beadata.screendata.mapper","cn.beadata.mapper","cn.beadata.third.api.shaoxing.xinchang.mapper"})
public class BulletsFlyApplication {
	public static void main(String[] args) {
		new Thread(()->{
			DaLingMqtt daLingMqtt = new DaLingMqtt();
			daLingMqtt.result();
		}).start();
		BladeApplication.run(BulletsFlyConstant.APPLICATION_BULLETS_FLY_NAME, BulletsFlyApplication.class, args);
	}

}

Enum

public enum ContentPublishTypeEnum {

    Deleted(0,"删除"),
    
    Published(1,"发布"),
    
    Saved(2,"保存未发布"),

    Disable(3,"禁用"),

    ;

    private Integer status;
    
    private String desc;

    public Integer getStatus() {
        return this.status;
    }

    ContentPublishTypeEnum(Integer status,String desc) {
        this.desc = desc;
        this.status = status;
    }
}

websocket

1. 导入依赖

由于websocket依赖包和SpringBoot内置的存在冲突,所以使用exclusions避开web包

            <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-web</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
2. 编写配置类
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * @author litongyin
 * @date 2023/1/30 18:18
 */
@Configuration
public class WebSocketConfig {

	/**
	 * 	注入ServerEndpointExporter,
	 * 	这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
	 */
	@Bean
	public ServerEndpointExporter serverEndpointExporter() {
		return new ServerEndpointExporter();
	}
}
3. 编写websocket服务端
@Component
@Slf4j
@ServerEndpoint("/websocket/{userId}")  // 接口路径 ws://localhost:xxxx/websocket/userId;
public class WebSocketServer {

    //与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;
        /**
     * 用户ID
     */
    private String userId;

    // 用来存在线连接用户信息
	private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();

    /**
     * 链接成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam(value="userId")String userId) {
        try {
			this.session = session;
			this.userId = userId;
			webSocketMap.put(userId,this);
			log.info("【websocket消息】userId: "+userId+" 连接成功,目前总数为:"+webSocketMap.size());

		} catch (Exception e) {
		}
    }

    /**
     * 链接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        try {
			webSocketMap.remove(userId);
			log.info("【websocket消息】userId: "+userId+" 连接断开,目前总数为:"+webSocketMap.size());
		} catch (Exception e) {
		}
    }
    /**
     * 收到客户端消息后调用的方法
     *
     * @param message
     */
    @OnMessage
    public void onMessage(String message) {
    	log.info("【websocket消息】userId: "+userId+" 收到客户端消息: "+message);
    }

	  /** 发送错误时的处理
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {

        log.error("【websocket消息】userId: "+userId+" 用户错误,原因:"+error.getMessage());
        error.printStackTrace();
    }

	// 此为单点消息
	@SneakyThrows
	public void sendMessage(String message) throws IOException {
		this.session.getBasicRemote().sendText(message);
	}

	/**
	 * 通过userId向客户端发送消息
	 */
	public void sendMessage(String userIdsStr, String message) throws IOException {
		log.info("【websocket消息】userIds: "+userIdsStr);
		log.info("【websocket消息】向客户端发送消息: "+message);
		String[] userIds = userIdsStr.split(",");
		for (String userId : userIds) {
			if(StrUtil.isNotBlank(userId) && webSocketMap.containsKey(userId)){
				webSocketMap.get(userId).sendMessage(message);
			}else{
				log.error("用户{}不在线",userId);
			}
		}
	}


    // 此为广播消息
    public void sendAllMessage(String message) throws IOException {
    	log.info("【websocket消息】广播消息: "+message);
		if(StrUtil.isNotBlank(userId) && webSocketMap.containsKey(userId)){
			webSocketMap.get(userId).sendMessage(message);
		}else{
			log.info("用户{}不在线",userId);
		}
    }

}

六、BladeX

多租户的数据隔离-tenant_id

bladeX框架提供了使用租户id字段来实现同一张数据表中不同租户数据的增删改查。

BladeX 框架将访问的用户对象封装成了BladeUser对象,直接在Controller层引用即可。

BladeUser(
    clientId=saber, 
    userId=1123598821738675201, 
    tenantId=000000, 
    deptId=1123598813738675201, 
    userName=admin, 
    account=admin, 
    roleId=1123598816738675201, 
    roleName=administrator
)

例子:管理员角色能查询所有用户,其他租户只能查询到tenant_id相同的用户

/**
	 * 用户列表
	 */
	@GetMapping("/list")
	@ApiImplicitParams({
		@ApiImplicitParam(name = "account", value = "账号名", paramType = "query", dataType = "string"),
		@ApiImplicitParam(name = "realName", value = "姓名", paramType = "query", dataType = "string")
	})
	@ApiOperationSupport(order = 3)
	@ApiOperation(value = "列表", notes = "传入account和realName")
	public R<IPage<UserVO>> list(@ApiIgnore @RequestParam Map<String, Object> user, Query query, BladeUser bladeUser) {
		System.out.println("======user:"+user);
		System.out.println("======query:"+query);
		System.out.println("======bladeUser:"+bladeUser);
		QueryWrapper<User> queryWrapper = Condition.getQueryWrapper(user, User.class);
		IPage<User> pages = userService.page(Condition.getPage(query), (!bladeUser.getTenantId().equals(BladeConstant.ADMIN_TENANT_ID)) ? queryWrapper.lambda().eq(User::getTenantId, bladeUser.getTenantId()) : queryWrapper);
		return R.data(UserWrapper.build().pageVO(pages));
	}

常见工具类

工具类包位置:org.springblade.core.tool.utils

CommonUtils - 脱敏工具

RedisUtil - 缓存工具

Func - 判断对象是否为空

SpringUtil - 根据类创建对象

多数据源配置

1.导入依赖

        <!-- 多数据源-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
            <version>2.5.6</version>
        </dependency>

2.修改配置文件

spring:
  autoconfigure:
    exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
  datasource:
    dynamic:
      #设置默认的数据源或者数据源组,默认值即为screen
      primary: bulletsfly
      datasource:
        # 默认数据源
        bulletsfly:
          url: ${meiyun.datasource.bulletsfly.url}
          username: ${meiyun.datasource.bulletsfly.username}
          password: ${meiyun.datasource.bulletsfly.password}
        # 第二个数据源
        crm:
          url: ${meiyun.datasource.crm.url}
          username: ${meiyun.datasource.crm.username}
          password: ${meiyun.datasource.crm.password}

3.在mapper上指定数据源(与配置文件中的数据源名称相同)

@DS("crm")
@Mapper
public interface ContractOrderMapper extends BaseMapper<ContractOrder> {
}

Redisson-分布式事务

1.单机模式-配置类

    @Value("${spring.redis.password}")
    private String password;

    @Value("${meiyun.lock.address}")
    private String address;

    @Value("${spring.redis.database}")
    private Integer database;

    @Bean
    public RedissonClient redissonClient(){
        Config config = new Config();
        String address = this.address;
        address = ((address.startsWith("redis://") ) ? address : "redis://"+address);
        SingleServerConfig serverConfig = config.useSingleServer().setAddress(address).setClientName("Single")
                .setDatabase(this.database);
        if(StringUtils.isNotBlank(this.password)) {
            serverConfig.setPassword(this.password);
        }
        return Redisson.create(config);
    }

2.哨兵模式-配置类

/**
 * @author litongyin
 * @date 2022/11/28 10:17
 */
@Configuration
public class CacheConfig {

    @Value("${spring.redis.password}")
    private String password;

    @Value("${spring.redis.sentinel.nodes}")
    private String address;

    @Value("${spring.redis.sentinel.master}")
    private String master;

    @Value("${spring.redis.database}")
    private Integer database;

    @Bean
    public RedissonClient redissonClient(){
        Config config = new Config();
        String[] adds = address.split(",");
        for (int i = 0; i < adds.length; i++) {
            StringBuilder stringBuilder = new StringBuilder("redis://");
            adds[i] = stringBuilder.append(adds[i]).toString();
        }
        SentinelServersConfig serverConfig = config.useSentinelServers()
                .addSentinelAddress(adds)
                .setMasterName(master)
                .setDatabase(database);
        serverConfig.setPassword(password);
        return Redisson.create(config);
    }
}

3.不同环境下的代码

@Data
@Component
@ConfigurationProperties(prefix = "spring.redis")
public class RedissonProperties {

    private String host;

    private String port;

    private String password;

    private MySentinelProperties sentinel;

    private Integer database;

}
@Configuration
public class CacheConfig {

    @Value("${meiyun.lock.active}")
    private String lockActive;

    @Autowired
    private RedissonProperties redissonProperties;

    @Bean
    public RedissonClient redissonClient(){
        Config config = new Config();
        if(!"prod".equals(lockActive)){ // 测试或开发

            String address = redissonProperties.getHost()+":"+redissonProperties.getPort();
            address = ((address.startsWith("redis://") ) ? address : "redis://"+address);
            SingleServerConfig serverConfig = config.useSingleServer().setAddress(address).setClientName("Single")
                    .setDatabase(redissonProperties.getDatabase());
            if(StringUtils.isNotBlank(redissonProperties.getPassword())) {
                serverConfig.setPassword(redissonProperties.getPassword());
            }
        } else { // 生产
            MySentinelProperties sentinel = redissonProperties.getSentinel();
            String[] adds = sentinel.getNodes().split(",");
            for (int i = 0; i < adds.length; i++) {
                StringBuilder stringBuilder = new StringBuilder("redis://");
                adds[i] = stringBuilder.append(adds[i]).toString();
            }
            SentinelServersConfig serverConfig = config.useSentinelServers()
                    .addSentinelAddress(adds)
                    .setMasterName(sentinel.getMaster())
                    .setDatabase(redissonProperties.getDatabase());
            serverConfig.setPassword(redissonProperties.getPassword());
        }
        return Redisson.create(config);
    }
}

4.使用

        String key = CacheNameConstant.COUPON_LOCK +userCouponReq.getCouponNo();
        RLock rLock = redissonClient.getLock(key);
        try {
            rLock.lock();
            XXX
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            rLock.unlock();
        }

参数校验

public class LoginCheck {

    public static void check(LoginReq loginReq){
        Assert.notNull(loginReq,"请求信息不能为空");
        Assert.notNull(loginReq.getUserType(),"用户类型不能为空");
        Assert.hasText(loginReq.getSourceType(),"来源类型不能为空");
        Assert.isTrue(Arrays.asList(UserTypeEnum.USER,UserTypeEnum.MERCHANT).contains(loginReq.getUserType()),"用户类型不正确");
        Assert.isTrue(Arrays.asList(SourceTypeEnum.WECHAT.name(),SourceTypeEnum.ALIPAY.name()).contains(loginReq.getSourceType()),"来源类型不正确");

    }
}

微信service

public interface WxService {

    public Map<String, String> code2session(final String code, String tenantId);

    /**
     * 获取code2session
     *
     * @param code
     * @return
     */

    Map<String,String> code2session(final String code) ;

    Map<String, String> code2sessionBy(String code, String tenantIdHead);

    /**
     * 获取AccessToken
     *
     * @return
     */
    String getAccessToken() ;


    /**
     * 检查文本内容是否涉敏
     * @param content
     * @return
     */
    boolean checkText(String content);

    /**
     * 检查图片内容是否涉敏
     * @param inputStream
     * @return
     */
    boolean checkImg(InputStream inputStream);

    /**
     * 检查内容
     * @param content
     * @param imgUrls
     * @return
     */
    boolean checkContent(String content,String imgUrls);

    /**
     * 解密手机号
     * @param sessionKey
     * @param encryptedData
     * @param iv
     * @return
     */
    String decryptPhone(String sessionKey, String encryptedData, String iv);

    String getUserPhoneNumber(String tenantId,String code);
}
@Service
@Slf4j
public class WxServiceImpl implements WxService {

    private static final Logger logger = LoggerFactory.getLogger(WxServiceImpl.class);
    /**
     * 微信accessToken存储
     */
    private static ConcurrentHashMap<String,String> ACCESSTOKEN_MAP = new ConcurrentHashMap<>();
    /**
     * 微信accessToken过期时间(ms)
     */
    private static ConcurrentHashMap<String,Long> ACCESSTOKEN_STRDATE_MAP = new ConcurrentHashMap<>();

    @Autowired
    private WeChatConf weChatConf;

    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private SaasService saasService;

    @Value("${wechat.get-user-phone-number}")
    private String GET_USER_PHONE_NUMBER;

    @Override
    public Map<String, String> code2session(final String code, String tenantId) {
        HashMap<String, String> map = new HashMap<>();
        map.put("appid", saasService.getAppIdByTenant(tenantId));
        map.put("secret", saasService.getAppSecretByTenant(tenantId));
        map.put("js_code", code);
        Map resMap = code2session(map);
        log.info("授权获取到的map:" + resMap);
        return resMap;
    }


    /**
     * 获取code2session
     *
     * @param code
     * @return
     */
    @Override
    public Map<String,String> code2session(final String code) {
        HashMap<String, String> map = new HashMap<>();
        String tenantId = AuthUtil.getTenantId();
        map.put("appid", saasService.getAppIdByTenant(tenantId));
        map.put("secret", saasService.getAppSecretByTenant(tenantId));
        map.put("js_code", code);
        Map resMap = code2session(map);
        return resMap;
    }

    @Override
    public Map<String, String> code2sessionBy(String code, String tenantId) {
        HashMap<String, String> map = new HashMap<>();
        map.put("appid", saasService.getAppIdByTenant(tenantId));
        map.put("secret", saasService.getAppSecretByTenant(tenantId));
        map.put("js_code", code);
        Map resMap = code2session(map);
        return resMap;
    }

    /**
     * 获取openId
     *
     * @param map
     * @return
     */
    private Map<String,Object> code2session(final Map<String, String> map) {
        map.put("grant_type", "authorization_code");
        final String response = HttpUtil.doPost(weChatConf.getGetOpenidUrl(), map);
        try {
            final JSONObject myJsonObject = JSONObject.parseObject(response);

            logger.info("WxServiceImpl.code2session res=" + myJsonObject.toJSONString());
            return myJsonObject;
        } catch (Exception e) {
            logger.error("WxServiceImpl.code2session response=" + response);
            throw ServiceException.ofResultEnum(ResultEnum.ERROR_GET_OPENID_FALT);
        }
    }

    /**
     * 获取AccessToken
     *
     * @return
     */
    private String accessToken() {
        String tenantId = AuthUtil.getTenantId();
        if (null==tenantId||""==tenantId){
            tenantId= cn.beadata.mct.cms.utils.AuthUtil.getTenantId();
        }
        String accessTokenUri = weChatConf.getGetAccesstokenPath() + "&appid=" + saasService.getAppIdByTenant(tenantId)
                + "&secret=" + saasService.getAppSecretByTenant(tenantId);
        logger.info("Enter GetAccessTokenUtil.getAccessToken accessTokenUri" + accessTokenUri);
        final String response = HttpUtil.doPost(accessTokenUri, null);
        try {
            final JSONObject accessTokenObject = JSONObject.parseObject(response);
            logger.info("Exit GetAccessTokenUtil.getAccessToken response=" + accessTokenObject);
            final String accessToken = accessTokenObject.get("access_token") == null ? null : accessTokenObject.get("access_token").toString();
            if (StringUtils.isEmpty(accessToken)) {
                throw ServiceException.ofResultEnum(ResultEnum.ERROR_GET_ACCESSTOKEN_FALT);
            }
            logger.info("Exit GetAccessTokenUtil.getAccessToken accessToken=" + accessToken);
            return accessToken;
        } catch (Exception e) {
            logger.error("Exit GetAccessTokenUtil.getAccessToken response=", response);
            throw ServiceException.ofResultEnum(ResultEnum.ERROR_GET_ACCESSTOKEN_FALT);
        }

    }

    private String getAccessToken(String tenantId) {
        if (null==tenantId||""==tenantId){
            tenantId= cn.beadata.mct.cms.utils.AuthUtil.getTenantId();
        }
        String accessTokenUri = weChatConf.getGetAccesstokenPath() + "&appid=" + saasService.getAppIdByTenant(tenantId)
                + "&secret=" + saasService.getAppSecretByTenant(tenantId);
        logger.info("Enter GetAccessTokenUtil.getAccessToken accessTokenUri" + accessTokenUri);
        final String response = HttpUtil.doPost(accessTokenUri, null);
        try {
            final JSONObject accessTokenObject = JSONObject.parseObject(response);
            logger.info("Exit GetAccessTokenUtil.getAccessToken response=" + accessTokenObject);
            final String accessToken = accessTokenObject.get("access_token") == null ? null : accessTokenObject.get("access_token").toString();
            if (StringUtils.isEmpty(accessToken)) {
                throw ServiceException.ofResultEnum(ResultEnum.ERROR_GET_ACCESSTOKEN_FALT);
            }
            logger.info("Exit GetAccessTokenUtil.getAccessToken accessToken=" + accessToken);
            return accessToken;
        } catch (Exception e) {
            logger.error("Exit GetAccessTokenUtil.getAccessToken response=", response);
            throw ServiceException.ofResultEnum(ResultEnum.ERROR_GET_ACCESSTOKEN_FALT);
        }

    }

    /**
     * 获取AccessToken
     *
     * @return
     */
    @Override
    public String getAccessToken() {
        final long millisecond = System.currentTimeMillis();
        String tenantId = AuthUtil.getTenantId();
        if (null==tenantId||""==tenantId) {
            tenantId= cn.beadata.mct.cms.utils.AuthUtil.getTenantId();
        }
        if ((millisecond - ACCESSTOKEN_STRDATE_MAP.getOrDefault(tenantId,0L)) >= weChatConf.getWxAccesstokenTimeout()) {
            final String accessToken = accessToken();
            ACCESSTOKEN_MAP.put(tenantId,accessToken);
            ACCESSTOKEN_STRDATE_MAP.put(tenantId,millisecond);
            return accessToken;
        } else {
            return ACCESSTOKEN_MAP.get(tenantId);
        }
    }

    /**
     * 检查文本内容是否涉敏
     *
     * @param content
     * @return
     */
    @Override
    public boolean checkText(String content) {
        String checkUrl = weChatConf.getMsgCheckUrl();
        String accessToken = this.getAccessToken();
        checkUrl = checkUrl + "?access_token=" + accessToken;

        JSONObject body = new JSONObject();
        body.put("content", content);

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);

        HttpEntity<Map> request = new HttpEntity<>(body, headers);

        JSONObject resJson = restTemplate.postForObject(checkUrl, request, JSONObject.class);

        Integer errcode = Integer.valueOf(String.valueOf(resJson.get("errcode")));
        if (errcode == 0) {
            return true;
        } else if (errcode == 87014) {
            return false;
        }
        logger.warn("checkContent failed , accessToken : {} , content : {} , error : {}", accessToken, content, resJson.toJSONString());
        throw ServiceException.ofResultEnum(ResultEnum.SENSITIVE_CONTENT_CHECK_FAILED);
    }

    /**
     * 检查图片内容是否涉敏
     *
     * @param inputStream
     * @return
     */
    @Override
    public boolean checkImg(InputStream inputStream) {
        String checkUrl = weChatConf.getImgCheckUrl();
        String accessToken = this.getAccessToken();
        checkUrl = checkUrl + "?access_token=" + accessToken;

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);

        HttpHeaders partHeaders = new HttpHeaders();
        partHeaders.setContentDispositionFormData("media", "media");
        HttpEntity<InputStreamResource> part = new HttpEntity<>(new InputStreamResource(inputStream), partHeaders);

        MultiValueMap<String, Object> multiValueMap = new LinkedMultiValueMap<>();
        multiValueMap.add("media", part);


        HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(multiValueMap, headers);

        JSONObject resJson = restTemplate.postForObject(checkUrl, request, JSONObject.class);

        Integer errcode = Integer.valueOf(String.valueOf(resJson.get("errcode")));
        if (errcode == 0) {
            return true;
        } else if (errcode == 87014) {
            return false;
        }
        logger.warn("checkContent failed , accessToken : {} , error : {}", accessToken, resJson.toJSONString());
        throw ServiceException.ofResultEnum(ResultEnum.SENSITIVE_CONTENT_CHECK_FAILED);
    }

    /**
     * 检查内容
     *
     * @param content
     * @param imgUrls
     * @return
     */
    @Override
    public boolean checkContent(String content, String imgUrls) {
        boolean checked = true;
        if (checked && !StringUtils.isBlank(content)) {
            checked = this.checkText(content);
        }
        if (checked && !StringUtils.isBlank(imgUrls)) {
            String[] imgs = imgUrls.split(",");
            for (String img : imgs) {
                if (checked) {
                    ResponseEntity<byte[]> responseEntity = restTemplate.exchange(img, HttpMethod.GET, HttpEntity.EMPTY, byte[].class);
                    ByteArrayInputStream bais = new ByteArrayInputStream(responseEntity.getBody());
                    checked = this.checkImg(bais);
                }
            }
        }
        return checked;
    }

    /**
     * 解密手机号
     *
     * @param sessionKey
     * @param encryptedData
     * @param iv
     * @return
     */
    @Override
    public String decryptPhone(String sessionKey, String encryptedData, String iv) {
        logger.info("sessionKey: {}",sessionKey);
        logger.info("encryptedData: {}",encryptedData);
        logger.info("iv: {}",iv);
        byte[] dataByte = Base64.decode(encryptedData);
        byte[] keyByte = Base64.decode(sessionKey);
        byte[] ivByte = Base64.decode(iv);

        try {
            // 如果密钥不足16位,那么就补足.  这个if 中的内容很重要
            int base = 16;
            if (keyByte.length % base != 0) {
                int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
                byte[] temp = new byte[groups * base];
                Arrays.fill(temp, (byte) 0);
                System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
                keyByte = temp;
            }
            // 初始化
            Security.addProvider(new BouncyCastleProvider());
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
            SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
            AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
            parameters.init(new IvParameterSpec(ivByte));
            cipher.init(Cipher.DECRYPT_MODE, spec, parameters);// 初始化
            byte[] resultByte = cipher.doFinal(dataByte);
            if (null != resultByte && resultByte.length > 0) {
                String result = new String(resultByte, "UTF-8");
                JSONObject jsonObject = JSONObject.parseObject(result);
                logger.info("decryptedData: {}",jsonObject.toJSONString());
                return String.valueOf(jsonObject.get("phoneNumber"));
            }
        } catch (Exception e) {
            logger.warn("decryptPhone failed",e);
            throw ServiceException.ofResultEnum(ResultEnum.ERROR_GET_PHONE_FALT);
        }
        return null;
    }

    @Override
    public String getUserPhoneNumber(String tenantId,String code) {
        String accessToken = getAccessToken(tenantId);
        String userPhoneNumberUri = GET_USER_PHONE_NUMBER + "?access_token=" + accessToken;
        Map<String, Object> param = new HashMap<>();
        param.put("code", code);
        log.info("Enter GET_USER_PHONE_NUMBER.getPhone userPhoneNumberUri" + userPhoneNumberUri + "param: " + code);
        JSONObject phoneNumberObject = HttpRequest.post(userPhoneNumberUri)
                .bodyJson(param)
                .execute()
                .onSuccess(responseSpec -> JSONObject.parseObject(responseSpec.asString()));
        try {
            log.info("Exit GetPhoneNumberUtil.getUserPhoneNumber response=" + phoneNumberObject);
            final String phone = phoneNumberObject.getJSONObject("phone_info").getString("purePhoneNumber");
            if (Func.isEmpty(phone)) {
                throw new org.springblade.core.log.exception.ServiceException(ResultEnum.ERROR_GET_PHONE_FALT.MSG());
            }
            log.info("Exit GetPhoneNumberUtil.getUserPhoneNumber accessToken=" + phone);
            return phone;
        } catch (Exception e) {
            log.error("Exit GetPhoneNumberUtil.getUserPhoneNumber response=", phoneNumberObject.toJSONString());
            throw new org.springblade.core.log.exception.ServiceException(ResultEnum.ERROR_GET_PHONE_FALT.MSG());
        }
    }


}

七、工具类

1. http工具类-后端访问接口获取数据

依赖
 <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpcore</artifactId>
            <version>4.4.5</version>
        </dependency>
代码
package com.spring.cache.utils;

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Slf4j
public class HttpUtil {

    public static String sendGet(String url, Map<String, Object> params,Map<String, Object> headerMap) throws URISyntaxException, IOException {
        // 创建Httpclient对象
        CloseableHttpClient httpclient = HttpClients.createDefault();
        // 定义请求的参数
        URIBuilder uriBuilder = new URIBuilder(url);

        if (params != null) {
            for (String key : params.keySet()) {
                uriBuilder.setParameter(key, params.get(key).toString());
            }
        }

        // 创建http GET请求
        HttpGet httpGet = new HttpGet(uriBuilder.build());
        //header对象
        if (headerMap != null) {
            for (String key : headerMap.keySet()) {
                httpGet.setHeader(key, headerMap.get(key).toString());
            }
        }

        //response 对象
        CloseableHttpResponse response = httpclient.execute(httpGet);
        String content = getResult(response);

        close(response, httpclient);
        return content;
    }

    public static String sendPost(String url, Map<String, Object> params,Map<String, Object> headerMap) throws IOException {
        // 创建Httpclient对象
        CloseableHttpClient httpclient = HttpClients.createDefault();
        // 创建http POST请求
        HttpPost httpPost = new HttpPost(url);

        if (params != null && params.size() > 0) {
            List<NameValuePair> parameters = new ArrayList<>(0);
            for (String key : params.keySet()) {
                parameters.add(new BasicNameValuePair(key, params.get(key).toString()));
            }
            // 构造一个form表单式的实体
            UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(parameters);
            // 将请求实体设置到httpPost对象中
            httpPost.setEntity(formEntity);
        }

        //伪装浏览器
        httpPost.setHeader("User-Agent",
                "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36");
        //header对象
        if (headerMap != null) {
            for (String key : headerMap.keySet()) {
                httpPost.setHeader(key, headerMap.get(key).toString());
            }
        }

        CloseableHttpResponse response = httpclient.execute(httpPost);
        // 判断返回状态是否为200
        String content = getResult(response);

        close(response, httpclient);
        return content;
    }

    public static String sendPost(String url, JSONObject json) throws IOException {
        // 创建Httpclient对象
        CloseableHttpClient httpclient = HttpClients.createDefault();
        // 创建http POST请求
        HttpPost httpPost = new HttpPost(url);

        StringEntity s = new StringEntity(json.toString());
        s.setContentEncoding("UTF-8");
        //发送json数据需要设置contentType
        s.setContentType("application/json");
        log.info("请求体body的数据:===================="+s);
        httpPost.setEntity(s);
        CloseableHttpResponse response = httpclient.execute(httpPost);

        // 判断返回状态是否为200
        String content = getResult(response);

        close(response, httpclient);
        return content;
    }

    public static String sendPost(String url, String json,Map<String, Object> headerMap) throws IOException {
        // 创建Httpclient对象
        CloseableHttpClient httpclient = HttpClients.createDefault();
        // 创建Http Post请求
        HttpPost httpPost = new HttpPost(url);
        // 创建请求内容
        StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
        httpPost.setEntity(entity);
        //header对象
        if (headerMap != null) {
            for (String key : headerMap.keySet()) {
                httpPost.setHeader(key, headerMap.get(key).toString());
            }
        }

        // 执行http请求
        CloseableHttpResponse response = httpclient.execute(httpPost);
        String content = getResult(response);

        close(response, httpclient);
        return content;
    }

    public static void close(CloseableHttpResponse response, CloseableHttpClient httpclient) throws IOException {
        if (response != null) {
            response.close();
        }
        httpclient.close();
    }

    public static String getResult(CloseableHttpResponse response) throws IOException {
        String content = "";
        if (response != null && response.getStatusLine().getStatusCode() == 200) {
            content = EntityUtils.toString(response.getEntity(), "UTF-8");
        }
        return content;
    }

    public static String sendPost(String url, Map<String, Object> params) throws IOException {
        // 创建Httpclient对象
        CloseableHttpClient httpclient = HttpClients.createDefault();
        // 创建http POST请求
        HttpPost httpPost = new HttpPost(url);

        if (params != null && params.size() > 0) {
            List<NameValuePair> parameters = new ArrayList<>(0);
            for (String key : params.keySet()) {
                parameters.add(new BasicNameValuePair(key, params.get(key).toString()));
            }
            // 构造一个form表单式的实体
            UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(parameters);
            // 将请求实体设置到httpPost对象中
            httpPost.setEntity(formEntity);
        }

        //伪装浏览器
        httpPost.setHeader("User-Agent",
                "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36");

        CloseableHttpResponse response = httpclient.execute(httpPost);
        if (response != null && response.getStatusLine().getStatusCode() == 200) {
            return EntityUtils.toString(response.getEntity(), "UTF-8");
        }
        return "";
    }

}


测试
@RestController
public class PersonController {

    @Autowired
    private PersonService personService;

    // 1. Get请求-路径参数
    @GetMapping("/findById/{id}")
    public Person findById(@PathVariable long id){
        return personService.findById(id);
    }

    //2. Get请求-请求参数
    @GetMapping("/findById")
    public Person findById2(@RequestParam long id){
        return personService.findById(id);
    }

    //3. Get请求-请求体Json格式
    @PostMapping("/save")
    public boolean save(@RequestBody Person person){
        return personService.save(person);
    }

    //4. Post请求-form表单格式
    @PostMapping("/saveForm")
    public boolean save2(Person person){
        return personService.save(person);
    }
}
@Test
    void http() throws URISyntaxException, IOException {
        // 1. Get请求-路径参数(127.0.0.1:8080/findById/1)
        String s = HttpUtil.sendGet("http://127.0.0.1:8080/findById/1", null, null);
        System.out.println(s);
        
        //2. Get请求-请求参数(127.0.0.1:8080/findById/?id=1)
        Map<String, Object> map = new HashMap<>();
        map.put("id",1);
        String s1 = HttpUtil.sendGet("http://127.0.0.1:8080/findById/", map, null);
        System.out.println(s1);
        
        //3. POST请求-请求体Json格式
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("name","li");
        jsonObject.put("age",250);
        String s2 = HttpUtil.sendPost("http://127.0.0.1:8080/save", jsonObject);
        System.out.println(s2);

        //4. Post请求-form表单格式
        Map<String,Object> map1 = new HashMap();
        map1.put("name","li");
        map1.put("age",250);
        String s3 = HttpUtil.sendPost("http://127.0.0.1:8080/saveForm", map1);
        System.out.println(s3);
    }

八、前端

小程序引入Vconsole

安装

npm install vconsole

在main.js中引入

import VConsole from 'vconsole';

const vConsole = new VConsole();

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BjliFyub-1674872708156)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220922110829175.png)]

Uniapp小程序去除导航栏

"navigationStyle":"custom"

在这里插入图片描述

Css实现省略号…及悬浮层显示全部内容的方法:

https://blog.csdn.net/weixin_53791978/article/details/122483191

微信小程序跳转h5

1.微信开放平台设置开放地址(https//域名)

2.运维在nginx下放置文件

3.微信端代码

let url = "https://partybuildh5-test.beadata.cn/#/mainPageAnder?loginType=weiXin&token="+getUserInfo().tongong_token
console.log("准备跳转智慧党建页面,url:",url)
uni.navigateTo({
    url: "/pagesC/partyBuiding/index?url="+encodeURIComponent(url)
});

4.h5端代码

<template>
    <web-view :src="url"></web-view>
</template>

<script>
import config from 'utils/config';
import { Request } from "utils/request";
import { getUserInfo } from "utils/tools";
export default {
   data() {
        return {
            url: ''
        }
    },
    onLoad(options){
        this.cutPartyBuilding(options)
    },
    methods: {
        cutPartyBuilding(options){
            this.url = decodeURIComponent(options.url)
        }
    }
}
</script>
<style lang='scss' scoped>
</style>

微信小程序内嵌h5打电话

<div>手机:<a :href="'tel:'+item.phone">{{ item.phone }}</a></div>

Vue修改代理请求

devServer: {
    open: true,
    proxy: {
      '/blade-party-admin': {
        // target: 'https://partybuildh5.beadata.cn',
        // target: 'http://47.98.213.13:8200',
        target: 'http://47.98.213.13:18202/api', // li
        logLevel: 'debug', // 控制台查看代理后请求
        changeOrigin: true,
        pathRewrite:{       //重写请求路径
          '/blade-party-admin' : '/blade-party-admin-wugen' 
        }

      }
    }
  }

Ant Design

表单控件

<template>
        <!--状态-->
         <a-input
           hidden
           v-decorator="[`status`]"
         ></a-input>
</template>

<script>
    export default{
        name : 'T',
        data(){
            return {
                form: this.$form.createForm(this, { name: "coordinated" }),
            }
        }
        methods: {
           formData(){
                // 获取表单控件的所有值
                const value = this.form.getFieldsValue()
                console.log("表单value",value)
                // 获取表单控件的搁置
                const h = this.form.getFieldsValue(["isHot"])
                console.log("表单isHot",h)
                // 设置表单的值
                this.form.setFieldsValue({"isHot":1})
                console.log("表单value",this.form.getFieldsValue())
               // 清空表单数据
               this.$nextTick(()=>{
                   this.form.resetFields()
               })
               
           }
        }
    }
</script>

Uniapp

vscode运行微信小程序

npm run dev:mp-weixin

Vue-websocket

1.导入依赖
npm install websocket
2.编写api(/api/socket.js)
import {setStore, getStore} from '@/util/store'

var websock = null;
var global_callback = null;
 
 
function getWebIP(){
	var curIP = "ws://localhost:8084/websocket/";
	return curIP;
}
 
function initWebSocket(userId){ //初始化weosocket
    //ws地址
    var wsuri = getWebIP()+userId;
    console.log("【websocket消息】 连接url:"+wsuri)
    websock = new WebSocket(wsuri);
    websock.onmessage = function(e){
    	websocketonmessage(e);
    } 
    websock.onclose = function(e){
    	websocketclose(e);
    }
    websock.onopen = function () {
	    websocketOpen();
	}
    
    //连接发生错误的回调方法
	websock.onerror = function () {
		console.log("【websocket消息】连接异常");
	}
}
 
// 实际调用的方法
function sendSock(agentData,callback){  
    global_callback = callback;
    if (websock.readyState === websock.OPEN) {
        //若是ws开启状态
        websocketsend(agentData)
    }else if (websock.readyState === websock.CONNECTING) {
    	// 若是 正在开启状态,则等待1s后重新调用
        setTimeout(function () {
        	sendSock(agentData,callback);
        }, 1000);
    }else {
    	// 若未开启 ,则等待1s后重新调用
        setTimeout(function () {
            sendSock(agentData,callback);
        }, 1000);
    }
}
 
//数据接收
function websocketonmessage(e){ 
    console.log("【websocket消息】接收信息: ",e.data)
    var data = JSON.parse(e.data)
    console.log("【websocket消息】接收信息: data: ",data)
    global_callback(JSON.parse(e.data));
}
 
//数据发送
function websocketsend(agentData){
    console.log("【websocket消息】发送信息: ",agentData);
    websock.send(JSON.stringify(agentData));
}
 
//关闭
function websocketclose(e){  
    console.log("【websocket消息】连接关闭 (" + e.code + ")");
}
 
function websocketOpen(e){
	console.log("【websocket消息】连接成功");
}
var userInfo = getStore({name: 'userInfo'})
initWebSocket(userInfo.userId)

export{sendSock}
3.在main.js中引用
// websocket
import * as socketApi from './api/socket'
Vue.prototype.socketApi = socketApi

Vue-弹幕功能

地址: https://github.com/superhos/vue-baberrage/blob/master/docs/zh/README.md

1. 安装
npm install 
2. 在main.js中引用
// vue-baberrage 弹幕播放
import { vueBaberrage } from 'vue-baberrage'
Vue.use(vueBaberrage)
3. 代码
<template>
  <basic-container>
    <vue-baberrage
      :barrageList="barrageData.list"
      :boxHeight="barrageData.boxHeight"
      :isShow="barrageData.isShow"
      :lanesCount="barrageData.lanesCount"
      :loop="barrageData.loop"
      :messageHeight="messageHeight"
      :throttleGap="throttleGap">
    </vue-baberrage>    
  </basic-container>
</template>

<script>
  import { MESSAGE_TYPE } from 'vue-baberrage'

  export default {
    data() {
      return {
        barrageData:{
          list: [],
          isShow: true,    	//是否展示弹幕
          loop: true,     	//是否循环播放
          boxHeight: 170,         	//高度
          messageHeight: 25,		//消息高度
          lanesCount: 4,			//泳道数量
          throttleGap: 5000,		//消息间隔
        }
      }
    },
    mounted() {
      this.addToList();
    },
    methods: {
      addToList() {
        let list = [
          {id: 1,avatar: 'https://api.ddkjt.com/api/img_1.php',msg: '第一条弹幕',time: 3},
          {id: 2,avatar: 'https://api.ddkjt.com/api/img_1.php',msg: '第二条弹幕',time: 4},
          {id: 3,avatar: 'https://api.ddkjt.com/api/img_1.php',msg: '第三条弹幕',time: 3},
          {id: 4,avatar: 'https://api.ddkjt.com/api/img_1.php',msg: '第四条弹幕',time: 4},
          {id: 5,avatar: 'https://api.ddkjt.com/api/img_1.php',msg: '第五条弹幕',time: 5},
              {id: 6,avatar: 'https://api.ddkjt.com/api/img_1.php',msg: '第六条弹幕',time: 6},
              {id: 7,avatar: 'https://api.ddkjt.com/api/img_1.php',msg: '第七条弹幕',time: 7},
              {id: 8,avatar: 'https://api.ddkjt.com/api/img_1.php',msg: '第八条弹幕',time: 8},
              {id: 9,avatar: 'https://api.ddkjt.com/api/img_1.php',msg: '第九条弹幕',time: 9},
              {id: 10,avatar: 'https://api.ddkjt.com/api/img_1.php',msg: '第十条弹幕',time: 10}
        ];
        list.forEach((v) => {
          this.barrageData.list.push({
            id: v.id,					//弹幕ID
            avatar: v.avatar,      		//头像
            msg: v.msg,             	//弹幕消息
            time: v.time,          		//屏幕展示时间
            type: MESSAGE_TYPE.NORMAL,	//类型
          });
        });
      }
    }
  }
</script>

<style>
</style>

Vue-视频播放

1. 安装依赖
# vue 2
npm install vue-video-player@4.x -S
npm install --save videojs-contrib-hls

# vue3
npm install vue-video-player
2.在main.js中引用
// vue-video-player
import 'video.js/dist/video-js.css'
import 'videojs-contrib-hls'
import { videoPlayer } from 'vue-video-player'
Vue.use( videoPlayer )
3.示例代码
<template>
     <video-player
      :playsinline="true"
      :options="playerOptions"
      @play="onPlayerPlay($event)"
      @pause="onPlayerPause($event)"
      @ended="onPlayerEnded($event)"
    >
    </video-player>
</template>

<script>
  import { MESSAGE_TYPE } from 'vue-baberrage'
  import { videoPlayer } from 'vue-video-player'

  export default {
    components: {
      videoPlayer
    },
    data() {
      return {
        playerOptions:{
          playbackRates: [0.5, 1.0, 1.5, 2.0], //可选择的播放速度
          autoplay: false, //如果true,浏览器准备好时开始回放。
          muted: false, // 默认情况下将会消除任何音频。
          loop: false, // 视频一结束就重新开始。
          preload: "auto", // 建议浏览器在<video>加载元素后是否应该开始下载视频数据。auto浏览器选择最佳行为,立即开始加载视频(如果浏览器支持)
          language: "zh-CN",
          aspectRatio: "3:2", // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3")
          fluid: true, // 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器。
          sources: [
            {
              type: "video/mp4",
              src: "https://how-are-you-li.oss-cn-hangzhou.aliyuncs.com/upload/20230130/9fbeaeae2a22e8c6ed5592888e55acd9.mp4"
            },
          ],
          poster: "https://how-are-you-li.oss-cn-hangzhou.aliyuncs.com/upload/20230130/15b4c7efaa74fd6742cf9461f7e441b3.jpg", //你的封面地址
          width: '200px', 
          height: '200px', 
          notSupportedMessage: "此视频暂无法播放,请稍后再试", //允许覆盖Video.js无法播放媒体源时显示的默认信息。
          controlBar: {
            timeDivider: true, //当前时间和持续时间的分隔符
            durationDisplay: true, //显示持续时间
            remainingTimeDisplay: false, //是否显示剩余时间功能
            fullscreenToggle: true, //全屏按钮
          },
        }
      }
    },
    mounted() {
    },
    methods: {
    }
  }
</script>

<style>
</style>

九、Git

IDEA-合并test分支到master分支

1.上传test分支
2.切换到master分支

在这里插入图片描述

3.合并test分支到master分支

在这里插入图片描述

4.上传到主分支

在这里插入图片描述

VsCode-合并分支

img

举例 将 dev 开发线 合并到 master

1 确定你在dev线,将dev代码改动全部提交
2 切换master,确定是最新代码,不确定就pull下,选择合并分支,见上图
3 在下拉的提示框中选择dev线,然后选择提交所有代码
4 切回到dev 继续开发

重点 merge命令的本质是从别的分支,将自身没有的提交记录拉去过来(粗略的说而已)。

命令行提交

git pull                      # 拉取代码
git add -u                    #(git add -u 暂存已修改过的)
git commit -m 'xxxxxxx'       # 提交,说明信息
git push -u origin 分支名      # 推送

十、其他

openssl

# 查看pem格式证书内容
openssl x509 -in cacert.pem -noout -text

# 查看证书序列号
openssl x509 -in apiclient_cert.pem -noout -serial

# 导出证书
openssl pkcs12 -clcerts -nokeys -in apiclient_cert.p12 -out apiclient_cert.pem -passin pass:1602904683

# 导出证书秘钥
openssl pkcs12 -nocerts -in apiclient_cert.p12 -out apiclient_key.pem -passin pass:1602904683 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值