### 0.欢迎参加23年软件杯a6/b4,申请环境时,推荐码填7068,凭截图可后台私戳我一对一答疑+送资料~
(宣传一波嘿嘿)
1.前言
- 项目来自2022年的中国软件杯A6赛题—智慧图书馆
- 基于“金蝶云·苍穹”平台完成图书馆功能的设计
- 项目基本是独立完成,最终有幸获得国家三等奖
2.项目展示
图书馆0802演示视频
3.项目描述
基于金蝶云·苍穹平台,通过社区自主学习并独立完成智慧图书馆项目的设计与实现
-> 功能:书籍检索,图书借还及预约委托,图书采购申请,借书单打印,个人中心,新增书籍,基础信息维护等
-> 涉及平台技术:页面开发,单据流转换,插件开发(Java)打印功能,到期预警,轻分析等
-> 项目设计:从基础资料(书籍-ISBN、书本-索书号、图书馆、中图法)到单据流转换(图书的采购申请单映射为图书采购订单);从书籍检索、借阅、申购到自动审批、书籍到期预警、轻分析卡片;
4.下一步(2022.10计划)
- 打算作为毕设~ 预计新添功能:图书智能推荐 √
- 参考:我的协同过滤实现
- 参考:参考itemcf代码
.
5.ItemCF实现(2023.3实现… 摆烂哈哈哈)
5.1 页面建模
其实就是单据啦
5.2 自动填充单据的相关代码(java插件,需要在页面注册)
package kd.lpqj.sa.plugins;
import akka.dispatch.Foreach;
import kd.bos.bill.AbstractBillPlugIn;
import kd.bos.dataentity.entity.DynamicObject;
import kd.bos.entity.datamodel.IDataModel;
import kd.bos.orm.query.QCP;
import kd.bos.orm.query.QFilter;
import kd.bos.servicehelper.BusinessDataServiceHelper;
import kd.bos.servicehelper.user.UserServiceHelper;
import kd.lpqj.sa.plugins.utils.Recommend;
import org.apache.commons.collections.map.HashedMap;
import java.util.ArrayList;
import java.util.EventObject;
import java.util.HashMap;
import java.util.List;
public class BookRecommendPlugin extends AbstractBillPlugIn {
@Override
public void afterCreateNewData(EventObject e) {
super.afterCreateNewData(e);
//用户设置为:当前用户
// DynamicObject dataEntity = this.getModel().getDataEntity();
long currentUserId = UserServiceHelper.getCurrentUserId();//获取当前用户id
QFilter qFilter = new QFilter("lpqj_borrowinfo.lpqj_borrow_person", QCP.equals, currentUserId);
QFilter qFilter2 = new QFilter("lpqj_borrowinfo.enable", QCP.equals, 1);
QFilter[] qFilters = {qFilter,qFilter2};
DynamicObject[] query_record = BusinessDataServiceHelper.load("lpqj_borrowinfo", "id,number,lpqj_borror_book",qFilters);
// lpqj_book_isbn lpqj_borror_book
QFilter[] qFilter3 = {new QFilter("lpqj_borrowinfo.lpqj_borrow_person", QCP.equals, currentUserId)};
//查出 书籍isbn表的数据,填充到单据体
int all = query_record.length;
HashMap<String,DynamicObject> recommendMap = new HashMap<String,DynamicObject>();
if(all > 0){
for (int i = 0; i < all; i++) {
QFilter[] qFilter_isbn = {new QFilter("number", QCP.equals, query_record[i].get("lpqj_borror_book.lpqj_textfield4"))};
DynamicObject[] queryIsbn = BusinessDataServiceHelper.load("lpqj_book_isbn", "id,number",qFilter_isbn);
System.out.println("queryIsbn[0]: "+queryIsbn[0]);//lpqj_book_isbn[id,masterid,name,number]
// //查出一条书籍isbn,如果已经存在,则跳过;不存在就加上
String isbn = query_record[i].get("lpqj_borror_book.lpqj_textfield4").toString();//978-7-111-67031-5
if(recommendMap.get(isbn)==null){
recommendMap.put(isbn,queryIsbn[0]);
}
}
}
//在 seqMap<id,isbn> 表中遍历,取出values(isbn)
// 根据isbn从recommendMap取出book
int index = 0;
List<DynamicObject> recommend = new Recommend().builderRecommend(currentUserId);
int size = recommend.size();
System.out.println(size);
if (size > 0) {
// 待新增分录的行数
IDataModel dataModel = this.getModel();
dataModel.batchCreateNewEntryRow("entryentity", size);
for (DynamicObject book : recommend) {
// 第 1 个参数(propName): 单据体列字段的字段标识
// 第 2 个参数(value):对应列字段待填充的值
// 第 3 个参数(rowIndex):行号, 从0开始计
dataModel.setValue("lpqj_isbn", book.get("id"), index++);
}
}
}
}
5.3 推荐算法代码(推荐算法函数)
package kd.lpqj.sa.plugins.utils;
import kd.bos.dataentity.entity.DynamicObject;
import kd.bos.orm.query.QCP;
import kd.bos.orm.query.QFilter;
import kd.bos.servicehelper.BusinessDataServiceHelper;
import java.util.*;
import java.util.stream.Collectors;
public class Recommend {
/*
在电影推荐系统的基础上修改 (可能存在用户数少于书籍数的情况)
int len = arr1.length>arr2.length?arr2.length:arr1.length; //原来是arr1.length
*/
public List<DynamicObject> builderRecommend(long UserId) {
// 查询所有订单记录
QFilter[] qFilter1 = {new QFilter("lpqj_borrowinfo.enable", QCP.equals, 1)};
DynamicObject[] borrowList = BusinessDataServiceHelper.load("lpqj_borrowinfo", "id,number,lpqj_borror_book,lpqj_borrow_person", qFilter1);
QFilter[] qFilter2 = {new QFilter("lpqj_book_isbn.enable", QCP.equals, 1)};
DynamicObject[] bookList = BusinessDataServiceHelper.load("lpqj_book_isbn", "id,number", qFilter2);
QFilter[] qFilter3 = {new QFilter("lpqj_bos_user_ext.enable", QCP.equals, 1)};
DynamicObject[] userList = BusinessDataServiceHelper.load("bos_user", "id,number", qFilter3);
//java.lang.RuntimeException: CAUSE: java.lang.NullPointerException
// 定义一个分数集合用户去存储用户行为,根据购买记录赋分,购买则算1分
List<ScoreDto> scoreList = new ArrayList<>();
for (DynamicObject borrow : borrowList) {
String isbn = (String) borrow.get("lpqj_borror_book.lpqj_textfield4");
Long user = (Long) borrow.get("lpqj_borrow_person.id");
ScoreDto scoreDto = new ScoreDto(user, isbn, 1d);
scoreList.add(scoreDto);
System.out.println("************借书记录:"+scoreDto.toString());
}
// 构建评分矩阵,行数是书籍数,列数是用户数
double[][] scoreArr = new double[bookList.length][userList.length];
for (ScoreDto scoreDto : scoreList) {
Long currentUserId = scoreDto.getUserId();
String currentBookIsbn = scoreDto.getbookId();
for (int i = 0; i < bookList.length; i++) {
DynamicObject book = bookList[i];
if (currentBookIsbn.equals(String.valueOf(book.get("number")))) {
for (int j = 0; j < userList.length; j++) {
DynamicObject user = userList[j];
if (currentUserId.equals(user.get("id"))) {
scoreArr[i][j] += scoreDto.getScore();
break;
}
}
break;
}
}
}
System.out.println("************打印评分矩阵************");
for (int i = 0; i < scoreArr.length; i++) {
for (int j = 0; j < scoreArr[i].length; j++) {
System.out.print(scoreArr[i][j] + "\t");
}
System.out.println();
}
// 根据评分矩阵构建相似度矩阵,基于物品协同,就是要求物品之间的相似度,所以二维数组的长和宽都是物品数
double[][] similarScoreArr = new double[bookList.length][userList.length];
for (int i = 0; i < bookList.length; i++) {
for (int j = i; j < bookList.length; j++) {
if (i == j) {
similarScoreArr[i][j] = 1;
} else {
// 应用余弦相似度计算
double[] arr1 = scoreArr[i];
double[] arr2 = scoreArr[j];
double molecule = 0;
double denominator1 = 0;
double denominator2 = 0;
for (int k = 0; k < arr1.length; k++) {
denominator1 += Math.pow(arr1[k], 2);
denominator2 += Math.pow(arr2[k], 2);
molecule += arr1[k] * arr2[k];
}
// 分母
double denominator = Math.sqrt(denominator1) * Math.sqrt(denominator2);
if (denominator != 0) {
double resultScore = molecule / denominator;
similarScoreArr[i][j] = resultScore;
similarScoreArr[j][i] = resultScore;
}
}
}
}
System.out.println("************打印相似度矩阵************");
for (int i = 0; i < similarScoreArr.length; i++) {
for (int j = 0; j < similarScoreArr[i].length; j++) {
System.out.print(similarScoreArr[i][j] + "\t");
}
System.out.println();
}
/*
接下来就是 相似度矩阵 * 评分矩阵 = 推荐矩阵,
这个思路就是一个用户对项目的评分是来源于其他那些与他相似的物品的评分
相似度越高,分数的影响则越大,这个时候回顾一下以前学的矩阵相乘,就是前一个矩阵的每一行 * 后一个矩阵的每一列
*/
// 定义推荐矩阵
double[][] recommendScoreArr = new double[bookList.length][userList.length];
/*
由于前一个矩阵每一行 * 后一个矩阵每一列算起来比较麻烦,所以把后一个矩阵进行倒置一下,行变列,列变行,
这样就变成前一个矩阵的每一行 * 后一个矩阵的每一行,比较容易计算和理解
*/
double[][] invertScoreArr = new double[userList.length][bookList.length];
for (int i = 0; i < scoreArr.length; i++) {
for (int j = 0; j < scoreArr[i].length; j++) {
invertScoreArr[j][i] = scoreArr[i][j];
}
}
System.out.println("************打印倒置后的评分矩阵************");
for (int i = 0; i < invertScoreArr.length; i++) {
for (int j = 0; j < invertScoreArr[i].length; j++) {
System.out.print(invertScoreArr[i][j] + "\t");
}
System.out.println();
}
for (int i = 0; i < recommendScoreArr.length; i++) {
for (int j = 0; j < recommendScoreArr[i].length; j++) {
double[] arr1 = similarScoreArr[i];
double[] arr2 = invertScoreArr[j];
double recommendScore = 0d;
int len = arr1.length>arr2.length?arr2.length:arr1.length; //原来是arr1.length
for (int k = 0; k < len; k++) { //arr1.length
recommendScore += arr1[k] * arr2[k];
}
recommendScoreArr[i][j] = recommendScore;
}
}
// 这里的推荐矩阵行数是书籍数量,列数是用户数,矩阵中的数据就是用户对书籍的预测喜好程度
System.out.println("************打印推荐矩阵************");
for (int i = 0; i < recommendScoreArr.length; i++) {
for (int j = 0; j < recommendScoreArr[i].length; j++) {
System.out.print(recommendScoreArr[i][j] + "\t");
}
System.out.println();
}
// 这里得到的矩阵实际上就是每个用户对每本书的预测评分,接下来看看要推荐的那个用户是哪一列数据,就把那一列数据取出来
// 然后再按预测的分数从高到低进行排序
int index = 0;
for (int i = 0; i < userList.length; i++) {
if ((Long) userList[i].get("id") == UserId) {
index = i;
break;
}
}
List<ItemScoreDto> itemScoreDtoList = new ArrayList<>();
for (int i = 0; i < recommendScoreArr.length; i++) {
itemScoreDtoList.add(new ItemScoreDto(recommendScoreArr[i][index], bookList[i]));
}
// 按预测分数从高到低排序
itemScoreDtoList.sort((o1, o2) -> o2.getItemScore().compareTo(o1.getItemScore()));
// 打印最终推荐
System.out.println("当前用户推荐的影片id为:");
itemScoreDtoList.forEach(book -> System.out.println(book.getItem().get("name")));
List<DynamicObject> collect = itemScoreDtoList.stream().map(ItemScoreDto::getItem).collect(Collectors.toList());
QFilter[] qFilter4 = {new QFilter("lpqj_borrowinfo.enable", QCP.equals, 1),
new QFilter("lpqj_borrowinfo.lpqj_borrow_person", QCP.equals, UserId)};
DynamicObject[] list = BusinessDataServiceHelper.load("lpqj_borrowinfo", "id,number,lpqj_borror_book,lpqj_borrow_person", qFilter4);
// 表示这个用户没有借过书
if (list.length == 0) {
return null;
}
// 去掉用户借过的书
List<String> borrow_isbn = new ArrayList<>();
List<DynamicObject> res = new ArrayList<DynamicObject>();
List<DynamicObject> collect_temp = new ArrayList<>();
for(DynamicObject recommend_book: collect){
collect_temp.add(recommend_book);
}
for(DynamicObject recommend_book: collect_temp){
for(DynamicObject borrow_record : list){
String isbn1 = recommend_book.get("number").toString();
String isbn2 = borrow_record.get("lpqj_borror_book.lpqj_textfield4").toString();
System.out.println("isbn:"+isbn1);
if(isbn1.equals(isbn2)&&collect.contains(recommend_book)){
collect.remove(recommend_book);
}
}
}
System.out.println("去重后推荐书籍数目:"+ collect.size());
if (collect.size() > 5) {
collect = collect.subList(0, 5);
}
return collect;
}
class ScoreDto {
private Long userId;
private String bookId;
private Double score;
public ScoreDto() {
}
public ScoreDto(Long userId, String bookId, Double score) {
this.userId = userId;
this.bookId = bookId;
this.score = score;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getbookId() {
return bookId;
}
public void setbookId(String bookId) {
this.bookId = bookId;
}
public Double getScore() {
return score;
}
public void setScore(Double score) {
this.score = score;
}
@Override
public String toString() {
return "ScoreDto{" +
"userId=" + userId +
", bookId='" + bookId + '\'' +
", score=" + score +
'}';
}
}
class ItemScoreDto {
private Double itemScore;
private DynamicObject book;
public ItemScoreDto() {
}
public ItemScoreDto(Double itemScore, DynamicObject book) {
this.itemScore = itemScore;
this.book = book;
}
public DynamicObject getItem() {
return book;
}
public void setItem(DynamicObject book) {
this.book = book;
}
public Double getItemScore() {
return itemScore;
}
public void setItemScore(Double itemScore) {
this.itemScore = itemScore;
}
}
}
5.4 推荐结果
6.最后
抱歉各位uu,本文源码不公开喔🤗
(其实2022年的苍穹搭建还是有点复杂的,导出来的项目不是纯代码,建议多了解一下哇🤗)
这个项目在参赛期间有配套课程辅助学习(如下图,但是泥萌好像看不了了555)
智慧图书馆管理系统及开发