如何开发微信小程序

news2025/1/13 17:04:49

前言

因为最近沉迷和朋友们一起下班去打麻将,他们推荐了一个计分的小程序,就不需要每局都转账或者用扑克牌记录了,但是这个小程序不仅打开有广告,各个页面都植入了广告,用起来十分不适。

于是我就心里暗自下定决心,一定要撸一个没有广告的小程序。一周后,这个小程序发布了。

欢迎大家参观和使用我的小程序!小程序名称:MahjongScorer

MahjongScorer

思路

1.注册,获取头像和昵称。已注册的用户直接自动登录。

2.创建房间,扫一扫加入房间或者转发微信好友、群聊,通过点击加入。

3.添加台板以记录每局抽出来的台费(无需台费可忽略)。

4.每一局对局结束后,记录每个人的输赢情况。

5.散场后,将本次游戏所有记录保存至个人历史记录中,解散房间。

6.评价,随机弹出评价页面对此次体验进行评价。

7.特色功能:为方便第 2 步的记录,增加长按语音识别。

准备工作

前端

工具:HBuilderX、微信开发者工具

框架:uni-app(Vue3)、pinia
由于页面简单,所以没有使用 UI 框架。

附 uni-app 基本项目结构:

┌─uniCloud 云空间目录,阿里云为 uniCloud-aliyun,腾讯云为 uniCloud-tcb(详见 uniCloud)
│─components 符合 vue 组件规范的 uni-app 组件目录
│ └─comp-a.vue 可复用的 a 组件
├─hybrid App 端存放本地 html 文件的目录
├─platforms 存放各平台专用页面的目录
├─pages 业务页面文件存放的目录
│ ├─index
│ │ └─index.vue index 页面
│ └─list
│ └─list.vue list 页面
├─static 存放应用引用的本地静态资源(如图片、视频等)的目录,注意:静态资源只能存放于此
├─uni_modules 存放 uni_modules 规范的插件。
├─wxcomponents 存放小程序组件的目录
├─main.js Vue 初始化入口文件
├─App.vue 应用配置,用来配置 App 全局样式以及监听 应用生命周期
├─manifest.json 配置应用名称、appid、logo、版本等打包信息
└─pages.json 配置页面路由、导航条、选项卡等页面类信息
1.新建项目

在 HBuilderX 新建项目。
vxmp2-1

2.配置开发工具路径

在 HBuilderX 配置微信开发者工具的安装路径:工具-设置-运行配置-小程序运行配置-微信开发者工具路径。
vxmp2-2

3.开启端口

在微信开发者工具,开启端口:设置-安全设置-服务端口。
vxmp2-3

4.运行项目

在 HBuilderX 运行项目:运行-运行到小程序模拟器-微信开发者工具。
vxmp2-4

这时会根据第 2 步配置的路径自动打开微信开发者工具。
vxmp2-5

至此,前端基础工作准备完成。如果您想写的小程序只有前端静态页面而无需后端服务,那么可以不用继续往下看了,只需要:

  • 编写静态页面的代码
  • 在微信开发者工具左上角点击登录,点击右上角详情-基本信息-AppId,点击修改为您注册的小程序 ID,点击上传。
    vxmp2-8
    • 其中 AppID 获取方式为:进入微信小程序官网,找到开发-开发管理-开发设置,在“开发者 ID”下即可获取 AppID(小程序 ID)
  • 找到管理-版本管理-开发版本,点击提交审核,待审核通过后将审核版本发布,即可完成小程序开发和发布。

后端

工具:IDEA

框架:SpringBoot

数据库:MySQL

1.基础工作

参考之前的博客:如何搭建自己的网站和如何搭建自己的网站(二)进行服务器的搭建和 jar 包的部署。

2.配置服务器域名

进入微信小程序官网,找到开发-开发管理-开发设置,在“开发者 ID”下获取 AppID(小程序 ID)、AppSecret(小程序密钥)并保存下来,后续接口需要使用。继续下滑至“服务器域名”,在 request 合法域名、uploadFile 合法域名、downloadFile 合法域名中填写服务器域名,即第 1 步基础工作中部署的服务器域名。

编写代码

前端

vxmp2-1 vxmp2-1

下面粘贴部分关键代码。

1.pinia 状态管理库

由于有很多公用的全局的属性和方法,所以将该部分内容都放在 pinia 的全局状态管理库里。

import { defineStore } from "pinia";

const useUserStore = defineStore("useUserStore", {
  state: () => {
    return {
      info: {
        // 用户信息
        openid: "",
        avatar: "",
        nickname: "",
        roomid: "",
      },
      isLogin: null, // 是否登录,用于判断是否显示登录页
      shareid: "", // 通过二维码或点击分享进入的房间id
      members: [], // 房间的成员
      records: [], // 本次游戏所有对局
      circle: 1, // 第几局
      sumArr: [], // 本次游戏目前总分
      timer: null, // 定时器,在生成房间后 2 秒触发一次监听房间成员的变更
      scene: null, // 场景:区分是否是通过朋友圈进入,朋友圈进入时无法获取openid会导致报错
      qrCode: "", // 房间二维码
      baseURL: "https://xxxxx.xxx/mahjong/", // 后端接口前缀,省去每次调用接口写一大堆前缀
    };
  },
  actions: {
    async updateInfo(data, mode) {}, // 添加用户、修改头像/昵称
		async getOpenid() {}, // 获取用户的openid
		async autoLogin(roomid) {}, // 如果缓存中有用户openid,则直接登录,否则获取用户openid,并存到缓存。
		async getRecords() {}, // 获取本次游戏所有对局
		async getMembers() {}, // 获取房间的成员
		getSum() {}, // 获取本次游戏目前总分
		async updateRoomid(roomid) {}, // 更新当前用户的房间id
		async gameOver() {}, // 结束游戏并解散房间
		setTimer() {}, // 设置定时器获取成员信息变更
});
2.注册

注册时,可以点击头像选择头像,不选就是默认头点击昵称输入昵称,昵称不可为空,且最多八个字。

<view class="container">
  <view class="avatarUrl">
    <button
      type="balanced"
      open-type="chooseAvatar"
      @chooseavatar="onChooseavatar"
    >
      <image
        :src="avatarUrl"
        class="refreshIcon"
        v-if="avatarUrl !== defaultAvatar"
      ></image>
      <image v-else src="/static/upload-avatar.svg" class="upload"></image>
    </button>
  </view>
  <view class="nickname">
    <input
      maxlength="8"
      type="nickname"
      :value="nickName"
      @blur="bindblur"
      placeholder="点击输入昵称"
      @input="bindinput"
    />
  </view>
  <view class="operation">
    <button class="confirm" @click="onSubmit">保存</button>
    <button v-if="userStore.info.nickname" class="cancel" @click="cancel">
      取消
    </button>
  </view>
</view>
3.首页

进入首页,点击自己头像可以修改头像、昵称,点击加号可以将房间分享联系人或群聊,其他用户可以通过点击分享加入房间。

长按加号可以生成台板,用于记录台费,点击台板可以删除台板。

点击扫码加入,可以生成当前房间小程序码,其他用户可通过扫描该小程序码加入房间。

点击开始,跳转到计分面板。点击结束,将会结束对局并解散当前房间,同时对局的得分情况也将保存至“记录”菜单。

async generateCode() { // 生成当前房间小程序码
  this.qrLoading = true
  uni.showLoading()
  let roomid = ''
  if (userStore.info.roomid) {
    roomid = userStore.roomid
  } else {
    roomid = md5(userStore.info.openid + new Date().getTime())
    userStore.updateRoomid(roomid)
  }
  let res = await uni.request({
    url: userStore.baseURL + `get-code?roomid=${userStore.info.roomid}`,
    method: 'post',
    responseType: 'arraybuffer',
  })
  userStore.qrCode = 'data:image/PNG;BASE64,' + uni.arrayBufferToBase64(res.data)
  uni.hideLoading()
  this.qrLoading = false
}
4.计分

在胜负栏选择胜负,在得分栏输入每个成员的得分。也可以不选择胜负,直接在得分栏输入正/负数。 最后一个成员的得分无需填写,将会根据所有成员得分之和为 0 的规则自动计算。

长按确定按钮可以进行语音识别,将识别出的结果自动填写在得分栏中。语音模板为: 昵称/第 n 个+输/赢/加/减/正/胜/负+多少。

startRecording() {
  if (this.isRecording) return;
  this.isRecording = true;
  this.recorderManager.start({
    duration: 60000, // 录音时长,单位为毫秒
    format: "mp3", // 录音格式
  });
  this.recognitionResult = "正在讲话…";
},
stopRecording() {
  if (!this.isRecording) return;
  this.isRecording = false;
  this.recorderManager.stop();
},
async handleRecordingStop(res) {
  let base64code = uni
    .getFileSystemManager()
    .readFileSync(res.tempFilePath, "base64");
  uni.showLoading();
  let rst = await uni.request({
    url: userStore.baseURL + "/translate/voice",
    method: "post",
    data: {
      data: base64code,
      customizationId: "xxxxxxxxxxxxxxxxxxxxx",
    },
    header: {
      "content-type": "application/x-www-form-urlencoded",
    },
  });
  uni.hideLoading();
  if (rst.data.Result) {
    this.recognitionResult = rst.data.Result;
    this.processingData();
  } else {
    this.recognitionResult = "";
    uni.showToast({
      title: "好像什么也没有听到~",
      icon: "none",
    });
  }
},
words2Number(words) { // 将句子转换成数字
  const one2ten = [
    "一",
    "二",
    "三",
    "四",
    "五",
    "六",
    "七",
    "八",
    "九",
    "十",
    "两",
  ];
  const wordArr = words.split("");
  let number = null;
  for (let i = 1; i < 12; i++) {
    if (wordArr.includes(one2ten[i - 1])) {
      number = i < 11 ? i : 2;
      break;
    }
  }
  return number;
},
processingData() {
  // 定义表示赢和输的意思的词及其对应的正负号
  const winKeywords = ["赢", "加", "正", "胜"];
  const loseKeywords = ["输", "减", "负"];
  const indexs = ["第一个", "第二个", "第三个", "第四个", "第五个"];

  // 初始化结果对象
  const result = {};

  // 按照逗号分割文本
  const phrases = this.recognitionResult.split(",");

  // 遍历每个短语
  phrases.forEach((phrase) => {
    let nickname = "";
    let score = 0;
    // 判断短语中是否包含赢和输的意思的词
    const winKeyword = winKeywords.find((keyword) =>
      phrase.includes(keyword)
    );
    if (winKeyword) {
      // 提取昵称和得分
      const data = phrase.split(winKeyword);
      if (data.length === 2) {
        nickname = data[0];
        indexs.forEach((x, i) => {
          if (nickname.includes(x))
            nickname = userStore.members[i].nickname;
        });
        const matchScore = data[1].match(/\d+/g);
        if (matchScore?.length) {
          score = 1 * matchScore[0];
        } else {
          score = this.words2Number(data[1]);
        }
      }
    } else {
      const loseKeyword = loseKeywords.find((keyword) =>
        phrase.includes(keyword)
      );
      if (loseKeyword) {
        // 提取昵称和得分
        const data = phrase.split(loseKeyword);
        if (data.length === 2) {
          nickname = data[0];
          indexs.forEach((x, i) => {
            if (nickname.includes(x))
              nickname = userStore.members[i].nickname;
          });
          const matchScore = data[1].match(/\d+/g);
          if (matchScore?.length) {
            score = -1 * matchScore[0];
          } else {
            score = -1 * this.words2Number(data[1]);
          }
        }
      }
    }
    // 添加到结果对象中
    if (nickname && score) result[nickname] = score;
  });
  if (Object.keys(result).length === 0) {
    uni.showToast({
      title: "没听清~",
      icon: "none",
    });
  } else {
    for (let key in result) {
      userStore.members.forEach((x, i) => {
        if (key.includes(x.nickname) || x.nickname.includes(key)) {
          this.scores[i] = Math.abs(result[key]);
          this.outcomes[i] = result[key] >= 0 ? "+" : "-";
        }
      });
    }
    this.autoWriteLast(); // 自动填写最后一个成员的得分
  }
}
5.记录

点击“记录”菜单,可以查看自己所有历史对局。

点击对局可以查看该对局详情。

6.详情

点击头像可以显示昵称。

async showUser(record, index) {
  let nickname = ''
  let openid = record.openids.split(',')[index]
  let findUser = this.viewd.find((x) => x.openid === openid)
  if (findUser) {
    nickname = findUser.nickname
  } else {
    let res = await uni.request({
      url: userStore.baseURL + `search-info`,
      method: 'get',
      data: { openid },
    })
    nickname = res.data[0].nickname
    this.viewd.push({ openid, nickname }) // 已经点过的不再调用接口
    console.log(this.viewd)
  }
  uni.showToast({
    title: nickname,
    icon: 'none',
  })
}
7.评价

首先在pages.json里面引入「评价发布组件」。

"plugins": {
	"wxacommentplugin": {
		"version": "latest",
		"provider": "wx82e6ae1175f264fa"
	}
}

如果还未添加插件,则在开发者工具Console里点击「添加插件」。
然后就可以在页面的js文件里面调用组件接口。

var plugin = requirePlugin("wxacommentplugin");
plugin.openComment({
  // wx_pay_id: '4200001729202306024807578', // 交易评价类账号选填
  success: (res)=>{
    console.log('plugin.openComment success', res)
  },
  fail: (res) =>{
    console.log('plugin.openComment fail', res)
  }
})

可在微信小程序官网的功能-体验评价中查看评价。

后端

1.建表

使用了两张表,一张用户表 mahjong,一张对局表 room。

vxmp2-6

id:用户id
openid:用户的 openid
avatar:用户的头像
nickname:用户的昵称
roomid:用户当前所在房间
updateTime: 用户加入房间时间

vxmp2-7

id:对局id
roomid:房间id
openids:该局对局的所有成员的openid
scores:该局对局的得分情况
circle:局数
createTime:该局对局结束时间
active:游戏是否结束
2.注册

① 前端通过 uni.login 这个请求获取 code,通过该 code(js_code) 结合小程序的 appid、secret 以及 grant_type=authorization_code,调用微信官方接口“https://api.weixin.qq.com/sns/jscode2session”,返回用户的openid。

public JSONObject getOpenid(String code) throws Exception {
    String appid = "xxx";
    String secret = "xxx";
    System.out.println("code=" + code);
    HttpClient httpClient = HttpClients.createDefault();
    URI url = new URIBuilder("https://api.weixin.qq.com/sns/jscode2session")
            .setParameter("appid", appid)
            .setParameter("secret", secret)
            .setParameter("js_code", code)
            .setParameter("grant_type", "authorization_code")
            .build();
    HttpGet httpGet = new HttpGet(url);
    JSONObject json = new JSONObject();
    try {
        HttpResponse res = httpClient.execute(httpGet);
        if (res.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
            String result = EntityUtils.toString(res.getEntity());// 返回json格式:
            json = json.parseObject(result);
        } else {
            throw new Exception("获取openid失败!");
        }
    } catch (Exception e) {
        throw new Exception("获取openid异常!");
    }
    return json;
}

② 在用户填完头像和昵称后,前端发送请求,后端将接收的头像存储至 minio 中并返回头像路径,然后结合 openid、昵称、房间 id 等信息存储到 mahjong 表中。

3.生成当前房间小程序码

先通过 appid、secre、grant_type=client_credential 调用微信官方接口获取 token,然后使用此 token 结合前端传递的当前房间 id,生成当前房间小程序码。

public byte[] getCode(String roomid) throws Exception {
    String appid = "xxx";
    String secret = "xxx";
    HttpClient httpClient = HttpClients.createDefault();
    URI tokenURI = new URIBuilder("https://api.weixin.qq.com/cgi-bin/token")
            .setParameter("appid", appid)
            .setParameter("secret", secret)
            .setParameter("grant_type", "client_credential")
            .build();
    HttpGet httpGet = new HttpGet(tokenURI);
    JSONObject json = new JSONObject();
    try {
        HttpResponse res = httpClient.execute(httpGet);
        if (res.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
            json = json.parseObject(EntityUtils.toString(res.getEntity()));
            URI codeURI = new URIBuilder("https://api.weixin.qq.com/wxa/getwxacode")
                    .setParameter("access_token", json.getString("access_token"))
                    .build();
            HttpPost httpPost = new HttpPost(codeURI);
            String body = "{\"path\": \"pages/home/index?roomid=" + roomid + "\"}";
            httpPost.setEntity(new StringEntity(body));
            try {
                HttpResponse rst = httpClient.execute(httpPost);
                if (rst.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                    return EntityUtils.toByteArray(rst.getEntity());
                } else {
                    throw new Exception("获取小程序码失败!");
                }
            } catch (Exception e) {
                throw new Exception("获取小程序码异常!");
            }
        } else {
            throw new Exception("获取token失败!");
        }
    } catch (Exception e) {
        throw new Exception("获取token异常!");
    }
}
4.语音识别

使用的是腾讯云的一句话识别,每个月免费 5000 次。前端将录音临时文件转换成 base64 编码传递至后端,结合使用场景 EngSerViceType、语音数据来源 SourceType(0:语音 URL;1:语音数据)、语音 Url、音频格式 VoiceFormat 以及时间戳、热词、自学习模型。

public JSONObject voiceTrans(@RequestParam(required = false) String engSerViceType, @RequestParam(required = false) Long sourceType,
                              @RequestParam(required = false) String url, @RequestParam(required = false) String voiceFormat,
                              @RequestParam(required = false) String data, @RequestParam(required = false) String customizationId ) {
    if (engSerViceType == null) engSerViceType = "16k_zh";
    if (sourceType == null) {
        if (url == null) sourceType = 1L;
        else sourceType = 0L;
    }
    if (voiceFormat == null) voiceFormat = "mp3";
    JSONObject json = new JSONObject();
    String result = VoiceUtils.voiceTrans(engSerViceType, sourceType, url, voiceFormat, data, customizationId);
    json = json.parseObject(result);
    return json;
}

public static String voiceTrans(String EngSerViceType, Long SourceType, String Url, String VoiceFormat, String Data, String CustomizationId ) {
    try {
        // 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取
        Credential cred = new Credential("xxx", "xxx");
        // 实例化一个http选项,可选的,没有特殊需求可以跳过
        HttpProfile httpProfile = new HttpProfile();
        httpProfile.setEndpoint("asr.ap-beijing.tencentcloudapi.com");
        // 实例化一个client选项,可选的,没有特殊需求可以跳过
        ClientProfile clientProfile = new ClientProfile();
        clientProfile.setHttpProfile(httpProfile);
        // 实例化要请求产品的client对象,clientProfile是可选的
        AsrClient client = new AsrClient(cred, "", clientProfile);
        // 实例化一个请求对象,每个接口都会对应一个request对象
        SentenceRecognitionRequest req = new SentenceRecognitionRequest();
        req.setEngSerViceType(EngSerViceType);
        req.setSourceType(SourceType);
        req.setUrl(Url);
        req.setVoiceFormat(VoiceFormat);
        req.setData(Data);
        req.setWordInfo(1L);
        req.setReinforceHotword(1L);
        req.setCustomizationId(CustomizationId);
        // 返回的resp是一个SentenceRecognitionResponse的实例,与请求对象对应
        SentenceRecognitionResponse resp = client.SentenceRecognition(req);
        // 输出json格式的字符串回包
        return SentenceRecognitionResponse.toJsonString(resp);
    } catch (TencentCloudSDKException e) {
        return e.toString();
    }
}
5.添加记录

当一局结束后,将房间 id,所有成员 openid,分数,局数存储至 room 表,并将 active 设置为 1。
先查询 mahjong 表中是否存在此房间 id,如果不存在,说明对局结束,房间已经解散。然后查询 room 表是否存在此房间 id 且 active 等于 1 的对局:① 如果不存在且 circle 等于 1,再判断该房间成员数量和提交的成员数量,如果前者大于后者,说明有新成员加入,此次提交不生效。② 如果不存在且 circle 不等于 1,说明已经不是第一局,房间已经锁定,其他人无法加入,所以不需要判断,提交有效。③ 如果存在且该房间最后一条对局记录的局数小于提交的局数,提交有效。④ 如果存在且该房间最后一条对局记录的局数大于或等于提交的局数,说明已经有人在你提交前提交过了,提交无效。

public String addRecord(Room record) {
    String openids = record.getOpenids();
    Integer circle = record.getCircle();
    List<Room> records = mahjongMapper.searchRecords(openids);
    List<Mahjong> mahjong = mahjongMapper.searchInfo("", record.getRoomid());
    if (mahjong.isEmpty()) {
        return "对局已结束";
    } else {
        if (records.isEmpty()) {
            if (circle == 1) {
                if (mahjong.size() > record.getOpenids().split(",").length) {
                    return "有新成员加入";
                }
            }
            mahjongMapper.addRecord(record);
            return "true";
        } else {
            if (records.get(records.size() - 1).getCircle() < circle) {
                mahjongMapper.addRecord(record);
                return "true";
            } else {
                return "其他成员已提交";
            }
        }
    }

}
6.结束游戏

前端将此次游戏所有成员 openid、所有对局的 id、总分、以及房间 id 传递给后端,后端所做的工作依次为:
① 通过 mahjongMapper.gameOver(openids),遍历 openids,将 mahjong 表中 openid 与之相等的用户房间 id 清空。
② 通过 mahjongMapper.setActive(ids),遍历所有对局 id,将 room 表中 id 与之相等的对局的 active 设置为 0。
③ 通过 mahjongMapper.addRecord(record),将总分作为一条对局记录添加至 room 表中,其中 circle 等于-1,active 等于 0。

public void gameOver(String[] openids, String[] ids, String scores, String roomid) {
    mahjongMapper.gameOver(openids);
    mahjongMapper.setActive(ids);
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    simpleDateFormat.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
    String createTime = simpleDateFormat.format(new Date());
    Room record = new Room(null, String.join(",", ids), String.join(",", openids), scores, -1, createTime, 0);
    mahjongMapper.addRecord(record);
}
7.查询历史记录

这个接口逻辑写的有点草率,后续可以优化。

先通过个人 openid 查询 room 表与之相关且 circle 等于-1 的历史记录,
然后查询 mahjong 表中所有用户信息,遍历历史记录,遍历用户信息,
将历史记录的 openid 等于用户信息的 openid 的用户头像提取出来,与历史记录一并返回。

public List searchHistory(String openid) {
    List<Room> records = mahjongMapper.searchHistory(openid);

    List<Mahjong> mahjongs = mahjongMapper.searchAllInfo();

    for (Room record : records) {
        String[] openids = record.getOpenids().split(",");
        String avatars = "";
        String avatar = "";

        for (String id : openids) {
            avatar = "";
            for (Mahjong mahjong: mahjongs)
            {
                if(mahjong.getOpenid().equals(id)){
                    avatar = mahjong.getAvatar();
                    break;
                }
            }
            avatars += avatar + ',';
        }
        record.setAvatars(avatars.substring(0, avatars.length() - 1));
    }
    return records;
}

最后

感兴趣的朋友可以私聊我获取详细代码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1104963.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

蓝桥杯每日一题2023.10.18

题目描述 特别数的和 - 蓝桥云课 (lanqiao.cn) 题目分析 简单枚举每一个可行的数 #include<bits/stdc.h> using namespace std; int flag, ans; int main() {int n;cin >> n;for(int i 1; i < n; i ){flag 0;int x i;while(x){int y x % 10;if(y 2 || y…

NewStarCTF2023week3-Rabin‘s RSA

根据题目提示是Rabin算法 先将N分解得到P和Q 导入e&#xff0c;n&#xff0c;p&#xff0c;q&#xff0c;c 使用Rabin算法直接计算明文&#xff0c;再将明文转字符串即可 我们也可以通过脚本来理解原理 import gmpy2 import libnump 13934102561950901579 q 144504527390…

全自动打包机检测不到货物怎么办?

全自动打包机也称无人化打包机或无人化捆扎机&#xff0c;是指在工作的过程中不需要人工干预&#xff0c;机器可以自动检测、自动输送、自动打包的设备。但最近有一些客户反映打包机不能自己检测到货物了。这是为什么呢&#xff1f; 1、电子眼感应太弱。电子眼的感应程度是可以…

棋盘覆盖问题(分治法)

裁判测试程序样例&#xff1a; #include <iostream> #include<fstream> #include <iomanip> #define MAX 1025 using namespace std; int board[MAX][MAX]; int tile1;void ChessBoard(int tr,int tc,int dr,int dc,int size);int main() { int dr,dc,size;…

GeoServer改造Springboot启动三(集成jdbcconfig和jdbcstore)

1、集成jdbc插件 1.1 由于GeoServer所有数据都在数据目录下,如果需要将数据存储数据库,这需要引入如图 13所示的两个插件。 图 13jdbc插件位置 1.2 右键两个插件的“pom.xml”,选择“Add as Maven Project”,模块就会加入maven关联。 图 14jdbc右键“Add as Maven Proje…

python代码调用文件或数据库中保存的脚本

这里采用的读取excel 1、先写一个测试方法 def demo5():import xlrdimport randomwb xlrd.open_workbook("code.xls")st wb.sheet_by_index(0)code st.cell_value(0, 0)list ["6666", asd, 1ad23, 1f23, 12g3, 1b3, 12r3]code2 st.cell_value(0, 1)…

Mendix 创客访谈录|用移动审批和三维可视化打造客户满意的高逻辑应用

本期创客 朱成 能科瑞元数字技术有限公司 Mendix业务部 朱成任职于能科瑞元数字技术有限公司Mendix业务部&#xff0c;主要负责工业互联网软件开发实施。本人主要负责Mendix Teamcenter集成开发、Mendix应用实施部署以及低代码平台技术支持。作为Mendix实施开发工程师参与了中…

iOS 借助定位实现“保活”策略

疑惑 你是否有过类似的体验,当你刚刚来到一个商业区,命名没打开任何APP,手机就会收到push给你推荐周围的“吃喝玩乐”,那他们又是怎么做到的呢? ##解密 其实,我们可以通过监听当位置变化,在用户无感知的情况下在后台悄悄拉齐我们的进行来处理特定的逻辑。 不是感觉很…

智能合同和TikTok:揭示加密技术的前景

在当今数字化时代&#xff0c;智能合同和加密技术都成为了技术和商业世界中的热门话题。它们代表了一个崭新的未来&#xff0c;有着潜在的巨大影响。 然而&#xff0c;你或许从未想过将这两者联系在一起&#xff0c;直到今天。本文将探讨智能合同和TikTok之间的联系&#xff0…

maven构建jar包运行后出现中文乱码问题解决

问题描述&#xff1a; 最近在接手一个坑时&#xff0c;发现本地打出来的jar包&#xff0c;到环境中运行之后总是出现中文乱码问题&#xff0c;但又不是全部中文乱码。经过排查发现&#xff0c;只有写在代码里的中文返回到前端之后出现了乱码。再通过解压打出来的jar包&#xff…

使用目标之间的先验关系提升目标检测器性能

今天跟大家分享阿姆斯特丹大学等提出的用于提升目标检测和实例分割性能的新方法RP-FEM&#xff0c;该方法将目标之间位置的先验关系融入到feature中。 论文标题&#xff1a;Relational Prior Knowledge Graphs for Detection and Instance Segmentation机构&#xff1a;阿姆斯特…

统计学习方法 EM 算法

文章目录 统计学习方法 EM 算法引入EM 算法EM 算法的导出EM 算法的收敛性三硬币模型 统计学习方法 EM 算法 学习李航《统计学习方法》时关于 EM 算法的笔记 引入 概率模型中有时候同时包含观测变量&#xff08;observable variable&#xff09;和隐变量&#xff08;潜在变量…

Spring Boot项目中使用 TrueLicense 生成和验证License(附源码)

1、Linux 在客户linux上新建layman目录&#xff0c;导入license.sh文件&#xff0c; [rootlocalhost layman]# mkdir -p /laymanlicense.sh文件内容&#xff1a; #!/bin/bash # 1.获取要监控的本地服务器IP地址 IPifconfig | grep inet | grep -vE inet6|127.0.0.1 | awk {p…

人脸写真FaceChain的简单部署记录(一)

由【让你拥有专属且万能的AI摄影师AI修图师——FaceChain迎来最大版本更新】这篇文章开始出发进行人脸写真的尝试&#xff0c;笔者之前modelscope申请过免费额度&#xff0c;这里有适配的GPU环境可以提供测试。 但是很难抢到GPU资源&#xff0c;需要等待很久&#xff0c;可能才…

路由侠内网穿透:轻松访问公司内部OA、ERP等系统,提高工作效率

前言: 本文介绍如何通过路由侠内网穿透&#xff0c;从外部网络环境&#xff08;比如异地和家中网络&#xff09;访问公司内网的 OA、ERP 等系统。 公司内网的 OA、ERP 以及其他类型的办公系统&#xff0c; 通常有两种形态&#xff0c;一种是网站形态&#xff0c;一种是单独的应…

开源IT资产管理系统Snipe-IT

本文完成于 8 月初&#xff0c;正好网友 chenlit 在找用于管理固定资产容器和套件&#xff0c;不知道 Snipe-IT 是否能满足他的需求&#xff1b; 什么是 Snipe-IT &#xff1f; Snipe-IT 是一个用 PHP 编写的免费开源 IT 资产管理系统。Snipe-IT 专为 IT 资产管理而设计&#x…

Spring后置处理器之AutowiredAnnotationBeanPostProcessor

抽象类InstantiationAwareBeanPostProcessorAdapter没有实现的接口方法&#xff0c;其子类必须实现。 抽象类InstantiationAwareBeanPostProcessorAdapter可以使得类InstantiationAwareBeanPostProcessorAdapter选择性实现接口部分抽象方法。

新年学新语言Go之二

一、前言 上文 新年学新语言Go之一讲了Go的牛逼之处&#xff0c;给自己找一个学习的理由&#xff0c;但是学任何一门语言基础语法还是要先掌握&#xff0c;虽然这是一件很枯燥的事。 二、环境搭建与HelloWorld 1、配置环境 从https://golang.google.cn/dl/下载Mac环境Go最新…

干洗店小程序,上门洗鞋洗衣下单算软件开发;

干洗店小程序&#xff0c;上门洗鞋洗衣软件&#xff1b; 有多个小程序开发案例 洗衣店小程序 是一款适用于洗衣店、干洗店等洗鞋店小程序&#xff1b; 干洗店小程序功能有: 1.(支持上门取送、送货到店、寄存网点、智能衣柜四种下单方式) 用户下单-上门取货拍照-送达门店工厂-入…

页面的动静分离

动静分离概念: 访问静态()和动态页面(tomcat)分开静态页面&#xff0c;由nginx提供服务&#xff0c;静态页面由tomcat提供服务。实现动态和静态页面负载均衡。 实验需求&#xff1a; 1台nginx代理 192.168.10.10 四层代理 2台nginx 提供静态页面 192.168.10.2…